Skip to content

Commit 2382b96

Browse files
eds: support decimal and hex literals for signed LowLimit/HighLimit
1 parent 4319d4a commit 2382b96

3 files changed

Lines changed: 55 additions & 18 deletions

File tree

canopen/objectdictionary/eds.py

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -218,14 +218,26 @@ def _calc_bit_length(data_type: int) -> int:
218218

219219
def _signed_int_from_hex(hex_str, bit_length):
220220
number = int(hex_str, 0)
221-
if number < 0 or number >= (1 << bit_length):
222-
raise ValueError(
223-
f"Value {hex_str!r} is out of range for a {bit_length}-bit signed integer"
224-
)
225-
max_value = (1 << (bit_length - 1)) - 1
226-
if number > max_value:
227-
return number - (1 << bit_length)
228-
return number
221+
min_signed = -(1 << (bit_length - 1))
222+
max_signed = (1 << (bit_length - 1)) - 1
223+
max_unsigned = (1 << bit_length) - 1
224+
225+
if number < 0:
226+
# Negative decimal literal (e.g. LowLimit=-32768)
227+
if number < min_signed:
228+
raise ValueError(
229+
f"Value {hex_str!r} is out of range for a {bit_length}-bit signed integer"
230+
)
231+
return number
232+
else:
233+
# Unsigned hex literal, two's-complement (e.g. LowLimit=0xFFFF → -1 for INTEGER16)
234+
if number > max_unsigned:
235+
raise ValueError(
236+
f"Value {hex_str!r} is out of range for a {bit_length}-bit signed integer"
237+
)
238+
if number > max_signed:
239+
return number - (1 << bit_length)
240+
return number
229241

230242

231243
def _convert_variable(node_id, var_type, value):

test/sample.eds

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -967,6 +967,24 @@ HighLimit=0x0A
967967
LowLimit=0x02
968968
PDOMapping=0
969969

970+
[3022]
971+
ParameterName=UNSIGNED16 decimal limits
972+
ObjectType=0x7
973+
DataType=0x06
974+
AccessType=rw
975+
LowLimit=100
976+
HighLimit=1000
977+
PDOMapping=0
978+
979+
[3023]
980+
ParameterName=INTEGER16 decimal limits
981+
ObjectType=0x7
982+
DataType=0x03
983+
AccessType=rw
984+
LowLimit=-100
985+
HighLimit=100
986+
PDOMapping=0
987+
970988
[3030]
971989
ParameterName=INTEGER32 only negative values
972990
ObjectType=0x7

test/test_eds.py

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -137,14 +137,16 @@ def test_record(self):
137137

138138
def test_record_with_limits(self):
139139
cases = [
140-
(0x3020, 0, 127), # INTEGER8
141-
(0x3021, 2, 10), # UNSIGNED8
142-
(0x3030, -2147483648, -1), # INTEGER32
143-
(0x3031, -1, 0), # INTEGER24
144-
(0x3032, -1, 0), # INTEGER40
145-
(0x3033, -1, 0), # INTEGER48
146-
(0x3034, -1, 0), # INTEGER56
147-
(0x3040, -10, +10), # INTEGER64
140+
(0x3020, 0, 127), # INTEGER8 hex limits
141+
(0x3021, 2, 10), # UNSIGNED8 hex limits
142+
(0x3022, 100, 1000), # UNSIGNED16 decimal limits
143+
(0x3023, -100, 100), # INTEGER16 decimal limits
144+
(0x3030, -2147483648, -1), # INTEGER32 hex limits
145+
(0x3031, -1, 0), # INTEGER24 hex limits
146+
(0x3032, -1, 0), # INTEGER40 hex limits
147+
(0x3033, -1, 0), # INTEGER48 hex limits
148+
(0x3034, -1, 0), # INTEGER56 hex limits
149+
(0x3040, -10, +10), # INTEGER64 hex limits
148150
]
149151
for index, expected_min, expected_max in cases:
150152
with self.subTest(index=f"0x{index:04X}"):
@@ -159,12 +161,17 @@ def test_signed_int_from_hex(self):
159161
result = _signed_int_from_hex('0x' + test_case["hex_str"], test_case["bit_length"])
160162
self.assertEqual(result, test_case["expected"])
161163

164+
def test_signed_int_from_hex_accepts_decimal(self):
165+
# Negative decimal values are valid EDS literals (CiA 306 allows both formats).
166+
self.assertEqual(_signed_int_from_hex("-1", 8), -1)
167+
self.assertEqual(_signed_int_from_hex("-128", 8), -128)
168+
self.assertEqual(_signed_int_from_hex("-2147483648", 32), -2147483648)
169+
162170
def test_signed_int_from_hex_rejects_out_of_range(self):
163-
# Value must fit in bit_length bits (unsigned representation).
164171
with self.assertRaises(ValueError):
165172
_signed_int_from_hex("0xFFFF", 8) # 16-bit value into 8-bit field
166173
with self.assertRaises(ValueError):
167-
_signed_int_from_hex("-1", 8) # negative inputs are never valid
174+
_signed_int_from_hex("-129", 8) # below minimum for 8-bit signed
168175

169176
def test_array_compact_subobj(self):
170177
array = self.od[0x1003]

0 commit comments

Comments
 (0)