Skip to content

Commit be9c56d

Browse files
authored
Disregard OD variable description in SdoClient.upload() (#592)
* Disregard OD variable description in SdoClient.upload(). The upload method should behave as a raw producer of byte data. Interpretation of the returned data based on the Object Dictionary is the responsibility of the SdoVariable class, which delegates to the generic ODVariable decoding functions. Move the data truncation to the method SdoVariable.get_data(), where access to the ODVariable is certain. The only truncation that still happens is based on the response size specified by the server, which might be smaller for e.g. expedited upload. Extend the upload() function docstring to clarify the changed behavior. Adjust the test case for expedited upload with unspecified size in the response, which is the only incompatible change. * Test expedited upload with 8 byte padded frame. The existing test response is actually wrong, since the standard mandates four bytes "data" regardless of the other flags.
1 parent 1582ba5 commit be9c56d

File tree

3 files changed

+41
-16
lines changed

3 files changed

+41
-16
lines changed

canopen/sdo/base.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -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(

test/test_sdo.py

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -90,8 +90,16 @@ def test_expedited_upload(self):
9090

9191
# UNSIGNED8 without padded data part (see issue #5)
9292
self.data = [
93-
(TX, b'\x40\x00\x14\x02\x00\x00\x00\x00'),
94-
(RX, b'\x4f\x00\x14\x02\xfe')
93+
(TX, b'\x40\x00\x14\x02\x00\x00\x00\x00'), # upload initiate 0x1400:02
94+
(RX, b'\x4f\x00\x14\x02\xfe'), # expedited, size=1
95+
]
96+
trans_type = self.network[2].sdo[0x1400]['Transmission type RPDO 1'].raw
97+
self.assertEqual(trans_type, 254)
98+
99+
# Same with padding to a full SDO frame
100+
self.data = [
101+
(TX, b'\x40\x00\x14\x02\x00\x00\x00\x00'), # upload initiate 0x1400:02
102+
(RX, b'\x42\x00\x14\x02\xfe\x00\x00\x00'), # expedited, no size indicated
95103
]
96104
trans_type = self.network[2].sdo[0x1400]['Transmission type RPDO 1'].raw
97105
self.assertEqual(trans_type, 254)
@@ -102,9 +110,9 @@ def test_size_not_specified(self):
102110
(TX, b'\x40\x00\x14\x02\x00\x00\x00\x00'),
103111
(RX, b'\x42\x00\x14\x02\xfe\x00\x00\x00')
104112
]
105-
# Make sure the size of the data is 1 byte
113+
# This method used to truncate to 1 byte, but returns raw content now
106114
data = self.network[2].sdo.upload(0x1400, 2)
107-
self.assertEqual(data, b'\xfe')
115+
self.assertEqual(data, b'\xfe\x00\x00\x00')
108116
self.assertTrue(self.message_sent)
109117

110118
def test_expedited_download(self):
@@ -131,6 +139,17 @@ def test_segmented_upload(self):
131139
device_name = self.network[2].sdo[0x1008].raw
132140
self.assertEqual(device_name, "Tiny Node - Mega Domains !")
133141

142+
def test_segmented_upload_too_much_data(self):
143+
# Server sends 5 bytes, but indicated size 4
144+
self.data = [
145+
(TX, b'\x40\x08\x10\x00\x00\x00\x00\x00'), # upload initiate, 0x1008:00
146+
(RX, b'\x41\x08\x10\x00\x04\x00\x00\x00'), # segmented, size indicated, 4 bytes
147+
(TX, b'\x60\x00\x00\x00\x00\x00\x00\x00'), # upload segment
148+
(RX, b'\x05\x54\x69\x6E\x79\x20\x00\x00'), # segment complete, 5 bytes
149+
]
150+
device_name = self.network[2].sdo[0x1008].raw
151+
self.assertEqual(device_name, "Tiny")
152+
134153
def test_segmented_download(self):
135154
self.data = [
136155
(TX, b'\x21\x00\x20\x00\x0d\x00\x00\x00'),

0 commit comments

Comments
 (0)