Skip to content

Commit a61d500

Browse files
author
Günther Jena
committed
fixed object_dictionary.write
1 parent 6d04457 commit a61d500

File tree

5 files changed

+121
-6
lines changed

5 files changed

+121
-6
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
1+
# 0.4.11
2+
3+
* check limits for numeric datatypes when using `object_dictionary.write`
4+
* check if object is existing when using `object_dictionary.write`
5+
16
# 0.4.10
7+
28
* fix upload of objects with size 0 (use segmented transfer instead of expetited)
39

410
# 0.4.9

requirements_dev.txt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
python-can==4.0.0
2-
pytest==6.2.4
3-
pytest-cov==2.12.1
1+
python-can==4.1.0
2+
pytest==7.2.2
3+
pytest-cov==4.0.0
44
flake8==3.9.2
55
mypy==0.960
66
rstcheck==6.0.0

src/durand/datatypes.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,3 +47,18 @@ def is_float(datatype: DatatypeEnum):
4747
DatatypeEnum.OCTET_STRING: Struct("s"),
4848
DatatypeEnum.DOMAIN: Struct("s"),
4949
}
50+
51+
52+
range_dict = {
53+
DatatypeEnum.BOOLEAN: (0, 1),
54+
DatatypeEnum.UNSIGNED8: (0, (2**8) - 1),
55+
DatatypeEnum.INTEGER8: (-(2**7), (2**7) - 1),
56+
DatatypeEnum.UNSIGNED16: (0, (2**16) - 1),
57+
DatatypeEnum.INTEGER16: (-(2**15), (2**15) - 1),
58+
DatatypeEnum.UNSIGNED32: (0, (2**32) - 1),
59+
DatatypeEnum.INTEGER32: (-(2**31), (2**31) - 1),
60+
DatatypeEnum.UNSIGNED64: (0, (2**64) - 1),
61+
DatatypeEnum.INTEGER64: (-(2**63), (2**63) - 1),
62+
DatatypeEnum.REAL32: (float("-inf"), float("inf")),
63+
DatatypeEnum.REAL64: (float("-inf"), float("inf")),
64+
}

src/durand/object_dictionary.py

Lines changed: 52 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import itertools
55
import logging
66

7-
from .datatypes import DatatypeEnum, struct_dict, is_numeric, is_float
7+
from .datatypes import DatatypeEnum, struct_dict, is_numeric, is_float, range_dict
88
from .callback_handler import CallbackHandler, FailMode
99

1010

@@ -27,15 +27,36 @@ class Variable:
2727
def __post_init__(self):
2828
if self.datatype not in DatatypeEnum:
2929
raise ValueError("Unsupported datatype")
30+
3031
if self.access not in ("rw", "ro", "wo", "const"):
3132
raise ValueError("Invalid access type")
33+
3234
if not is_numeric(self.datatype) and (
3335
self.maximum is not None or self.minimum is not None
3436
):
3537
raise ValueError(
3638
f"Minimum and Maximum not available with datatype {self.datatype!r}"
3739
)
3840

41+
if range_dict.get(self.datatype, None) is not None:
42+
minimum, maximum = range_dict[self.datatype]
43+
44+
if self.minimum is not None and self.minimum < minimum:
45+
raise ValueError(
46+
f"Specified minimum of {self.minimum} is lower than datatype supported minimum of {minimum}"
47+
)
48+
49+
if self.maximum is not None and self.maximum > maximum:
50+
raise ValueError(
51+
f"Specified maximum of {self.maximum} is higher than datatype supported maximum of {maximum}"
52+
)
53+
54+
if self.minimum is None:
55+
self.minimum = minimum
56+
57+
if self.maximum is None:
58+
self.maximum = maximum
59+
3960
@property
4061
def writable(self):
4162
return self.access in ("wo", "rw")
@@ -178,15 +199,27 @@ def __setitem__(self, index: int, obj: TObject):
178199
self._objects[index] = obj
179200

180201
def lookup(self, index: int, subindex: int = None) -> TObject:
202+
"""Return object on index:subindex in object dictionary. When subindex is None
203+
the object is returned. Otherwise a lookup is extended with subindex in the Array/Record.
204+
205+
:param index: index in object dictionary
206+
:param subindex: optional subindex to lookup inside object
207+
:returns: the Variable, Record or Array
208+
209+
:raises KeyError: when object not found in dictionary
210+
"""
181211
try:
182-
return self._variables[index]
183-
except KeyError:
212+
if index in self._variables:
213+
return self._variables[index]
214+
184215
if subindex is None:
185216
return self._objects[index]
186217

187218
obj = self._objects[index]
188219
assert isinstance(obj, (Record, Array)), "Record or Array expected"
189220
return obj[subindex]
221+
except KeyError:
222+
raise KeyError("Object {index}:{subindex} not in object dictionary")
190223

191224
def write(self, index: int, subindex: int, value: Any, downloaded: bool = False):
192225
"""Write the given value to the according variable.
@@ -197,6 +230,9 @@ def write(self, index: int, subindex: int, value: Any, downloaded: bool = False)
197230
:param value: value to be written
198231
:param downloaded: flag is set, when the write is caused by an actual download
199232
(instead of a internal value change)
233+
234+
:raises KeyError: when index:subindex not found
235+
:raises Exception: when validate_callback fails
200236
"""
201237
assert isinstance(
202238
value, (bytes, bool, int, float)
@@ -207,6 +243,19 @@ def write(self, index: int, subindex: int, value: Any, downloaded: bool = False)
207243
else:
208244
multiplexor = (index, subindex)
209245

246+
variable = self.lookup(*multiplexor) # check if variable exists
247+
248+
if not downloaded:
249+
if variable.minimum is not None and value < variable.minimum:
250+
raise ValueError(
251+
f"Value {value} is too low (minimum is {variable.minimum})"
252+
)
253+
254+
if variable.maximum is not None and value > variable.maximum:
255+
raise ValueError(
256+
f"Value {value} is too high (minimum is {variable.maximum})"
257+
)
258+
210259
if multiplexor in self.validate_callbacks:
211260
self.validate_callbacks[multiplexor].call(value) # may raises exception
212261

tests/test_object_dictionary.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,46 @@
1+
""" Testing object dictionary functionality """
2+
import re
3+
14
import pytest
5+
6+
from durand import Node, Variable
7+
from durand.datatypes import DatatypeEnum as DT
8+
9+
from .mock_network import MockNetwork
10+
11+
12+
def test_write():
13+
# create the node
14+
network = MockNetwork()
15+
node = Node(network, node_id=2)
16+
17+
# add a variable with index 0x2000:0 to the object dictionary of the node
18+
node.object_dictionary[0x2000] = Variable(DT.INTEGER16, "rw", value=5)
19+
assert node.object_dictionary.read(0x2000, 0) == 5
20+
21+
# these test should pass
22+
node.object_dictionary.write(0x2000, 0, 32767)
23+
assert node.object_dictionary.read(0x2000, 0) == 32767
24+
25+
node.object_dictionary.write(0x2000, 0, -32768)
26+
assert node.object_dictionary.read(0x2000, 0) == -32768
27+
28+
# test too low value
29+
with pytest.raises(ValueError, match=re.escape('Value -32769 is too low (minimum is -32768)')):
30+
node.object_dictionary.write(0x2000, 0, value=-32769)
31+
32+
# test too high value
33+
with pytest.raises(ValueError, match=re.escape('Value 32768 is too high (minimum is 32767)')):
34+
node.object_dictionary.write(0x2000, 0, value=32768)
35+
36+
37+
def test_access_of_unknown_variable():
38+
# create the node
39+
network = MockNetwork()
40+
node = Node(network, node_id=2)
41+
42+
with pytest.raises(KeyError):
43+
node.object_dictionary.write(0x2000, 0, 5)
44+
45+
with pytest.raises(KeyError):
46+
node.object_dictionary.read(0x2000, 0)

0 commit comments

Comments
 (0)