@@ -35,7 +35,15 @@ def __init__(self, rx_cobid, tx_cobid, node):
3535 def on_request (self , can_id , data , timestamp ):
3636 logger .debug ('on_request' )
3737 if self .sdo_block and self .sdo_block .state != BLOCK_STATE_NONE :
38- self .process_block (data )
38+ try :
39+ self .process_block (data )
40+ except SdoAbortedError as exc :
41+ self .sdo_block = None
42+ self .abort (exc .code )
43+ except Exception as exc :
44+ self .sdo_block = None
45+ self .abort ()
46+ logger .exception (exc )
3947 return
4048
4149 command , = struct .unpack_from ("B" , data , 0 )
@@ -137,6 +145,46 @@ def process_block(self, request):
137145 elif BLOCK_STATE_DOWNLOAD < self .sdo_block .state :
138146 # in download state
139147 logger .debug ('BLOCK_STATE_DOWNLOAD' )
148+ if self .sdo_block .state == BLOCK_STATE_DL_DATA :
149+ logger .debug ('BLOCK_STATE_DL_DATA' )
150+ seqno = command & 0x7F
151+ last_seg = bool (command & NO_MORE_BLOCKS )
152+ # Accumulate data bytes (bytes 1-7 of each segment)
153+ self .sdo_block .append_download_data (request [1 :8 ])
154+ self .sdo_block .last_seqno = seqno
155+
156+ if seqno >= self .sdo_block .req_blocksize or last_seg :
157+ # Send block acknowledgement
158+ response = bytearray (8 )
159+ response [0 ] = RESPONSE_BLOCK_DOWNLOAD | BLOCK_TRANSFER_RESPONSE
160+ response [1 ] = seqno # ackseq
161+ response [2 ] = self .sdo_block .req_blocksize # new blksize
162+ self .send_response (response )
163+ self .sdo_block .seqno = 0
164+
165+ if last_seg :
166+ self .sdo_block .update_state (BLOCK_STATE_DL_END )
167+
168+ elif self .sdo_block .state == BLOCK_STATE_DL_END :
169+ logger .debug ('BLOCK_STATE_DL_END' )
170+ if (command & REQUEST_BLOCK_DOWNLOAD ) != REQUEST_BLOCK_DOWNLOAD :
171+ raise SdoBlockException ("Unknown SDO command specified" )
172+ if (command & SUB_COMMAND_MASK ) != END_BLOCK_TRANSFER :
173+ raise SdoBlockException ("Unknown SDO command specified" )
174+
175+ # n = bytes NOT used in last segment
176+ n = (command >> 2 ) & 0x7
177+ data = self .sdo_block .finalize_download (n )
178+
179+ self ._node .set_data (self .sdo_block .index ,
180+ self .sdo_block .subindex ,
181+ data ,
182+ check_writable = True )
183+
184+ response = bytearray (8 )
185+ response [0 ] = RESPONSE_BLOCK_DOWNLOAD | END_BLOCK_TRANSFER
186+ self .send_response (response )
187+ self .sdo_block = None
140188 else :
141189 # in neither
142190 raise SdoBlockException ("Data can not be transferred or stored to the application "
@@ -229,13 +277,22 @@ def request_aborted(self, data):
229277 logger .info ("Received request aborted for 0x%04X:%02X with code 0x%X" , index , subindex , code )
230278
231279 def block_download (self , data ):
232- # We currently don't support BLOCK DOWNLOAD
233- # Unpack the index and subindex in order to send appropriate abort
280+ logger .debug ('Enter server block download' )
234281 command , index , subindex = SDO_STRUCT .unpack_from (data )
282+
235283 self ._index = index
236284 self ._subindex = subindex
237- logger .error ("Block download is not supported" )
238- self .abort (ABORT_INVALID_COMMAND_SPECIFIER )
285+
286+ self .sdo_block = SdoBlock (self ._node , data , is_download = True )
287+
288+ res_command = RESPONSE_BLOCK_DOWNLOAD | INITIATE_BLOCK_TRANSFER
289+ res_command |= self .sdo_block .crc # Echo CRC support back to client
290+ response = bytearray (8 )
291+ SDO_STRUCT .pack_into (response , 0 , res_command , index , subindex )
292+ response [4 ] = self .sdo_block .req_blocksize # Server-defined block size
293+
294+ self .sdo_block .update_state (BLOCK_STATE_DL_DATA )
295+ self .send_response (response )
239296
240297 def init_download (self , request ):
241298 # TODO: Check if writable (now would fail on end of segmented downloads)
@@ -345,38 +402,56 @@ class SdoBlock():
345402 crc_value = 0
346403 last_seqno = 0
347404
348- def __init__ (self , node , request , docrc = False ):
405+ def __init__ (self , node , request , docrc = False , is_download = False ):
349406 """
350407 :param node:
351408 Node object owning the server
352409 :param request:
353410 CAN message containing SDO request.
354411 :param docrc:
355412 If True, CRC is calculated and checked.
413+ :param is_download:
414+ If True, initialise for block download (server receives data).
415+ If False (default), initialise for block upload (server sends data).
356416 """
357417 command , index , subindex = SDO_STRUCT .unpack_from (request )
358418 # only do crc if crccheck lib is available _and_ if requested
359419 _req_crc = (command & CRC_SUPPORTED ) == CRC_SUPPORTED
360420
361- if (command & SUB_COMMAND_MASK ) == INITIATE_BLOCK_TRANSFER :
421+ # For block download, bit 1 is the size indicator (s), not a sub-command
422+ # bit. Only bit 0 carries the sub-command (0 = initiate). For block
423+ # upload the s-bit is not used in the initiate command so SUB_COMMAND_MASK
424+ # works there, but we must use a 1-bit mask here.
425+ sub_cmd_mask = 0x1 if is_download else SUB_COMMAND_MASK
426+ if (command & sub_cmd_mask ) == INITIATE_BLOCK_TRANSFER :
362427 self .state = BLOCK_STATE_INIT
363428 else :
364429 raise SdoBlockException ("Unknown SDO command specified" )
365430
366431 # TODO: CRC of data if requested
367- self .crc = CRC_SUPPORTED if (docrc & _req_crc ) else 0
432+ self .crc = CRC_SUPPORTED if (docrc & _req_crc ) else 0
368433 self ._node = node
369434 self .index = index
370435 self .subindex = subindex
371- self .req_blocksize = request [4 ]
372436 self .seqno = 0
373- if not 1 <= self .req_blocksize <= 127 :
374- raise SdoBlockException ("Invalid block size" )
375437
376- self .data = self ._node .get_data (index ,
377- subindex ,
378- check_readable = True )
379- self .size = len (self .data )
438+ if is_download :
439+ # Server defines the block size for download (client sends this many
440+ # segments per block before waiting for an acknowledgement)
441+ self .req_blocksize = 127
442+ self ._data_buffer = bytearray ()
443+ if command & BLOCK_SIZE_SPECIFIED :
444+ self .size , = struct .unpack_from ("<L" , request , 4 )
445+ else :
446+ self .size = None
447+ else :
448+ self .req_blocksize = request [4 ]
449+ if not 1 <= self .req_blocksize <= 127 :
450+ raise SdoBlockException ("Invalid block size" )
451+ self .data = self ._node .get_data (index ,
452+ subindex ,
453+ check_readable = True )
454+ self .size = len (self .data )
380455
381456 def update_state (self , new_state ):
382457 """
@@ -432,3 +507,25 @@ def get_data_byte(self):
432507 return self .data [self .data_uploaded - 1 ]
433508 return None
434509
510+ def append_download_data (self , segment ):
511+ """Append a 7-byte segment to the download data buffer.
512+
513+ :param segment:
514+ Bytes 1-7 of the received block segment message (always 7 bytes).
515+ """
516+ self ._data_buffer .extend (segment )
517+
518+ def finalize_download (self , n ):
519+ """Return the accumulated download data, trimming the last n unused bytes.
520+
521+ :param int n:
522+ Number of bytes in the last segment that did not contain data
523+ (as signalled by the client in the END_BLOCK_TRANSFER command).
524+
525+ :returns:
526+ The complete received data as bytes.
527+ """
528+ if n > 0 :
529+ return bytes (self ._data_buffer [:- n ])
530+ return bytes (self ._data_buffer )
531+
0 commit comments