🔨 Abort firmware update on transfer error (#24472, #24499)

This commit is contained in:
GHGiampy 2022-07-14 03:25:35 +02:00 committed by Scott Lahteine
parent efe04e1016
commit e840015cad
2 changed files with 111 additions and 42 deletions

View File

@ -376,11 +376,13 @@ class FileTransferProtocol(object):
token, data = self.await_response(1000) token, data = self.await_response(1000)
if token == 'PFT:success': if token == 'PFT:success':
print("File closed") print("File closed")
return return True
elif token == 'PFT:ioerror': elif token == 'PFT:ioerror':
print("Client storage device IO error") print("Client storage device IO error")
return False
elif token == 'PFT:invalid': elif token == 'PFT:invalid':
print("No open file") print("No open file")
return False
def abort(self): def abort(self):
self.protocol.send(FileTransferProtocol.protocol_id, FileTransferProtocol.Packet.ABORT); self.protocol.send(FileTransferProtocol.protocol_id, FileTransferProtocol.Packet.ABORT);
@ -417,12 +419,23 @@ class FileTransferProtocol(object):
self.write(data[start:end]) self.write(data[start:end])
kibs = (( (i+1) * block_size) / 1024) / (millis() + 1 - start_time) * 1000 kibs = (( (i+1) * block_size) / 1024) / (millis() + 1 - start_time) * 1000
if (i / blocks) >= dump_pctg: if (i / blocks) >= dump_pctg:
print("\r{0:2.2f}% {1:4.2f}KiB/s {2} Errors: {3}".format((i / blocks) * 100, kibs, "[{0:4.2f}KiB/s]".format(kibs * cratio) if compression_support else "", self.protocol.errors), end='') print("\r{0:2.0f}% {1:4.2f}KiB/s {2} Errors: {3}".format((i / blocks) * 100, kibs, "[{0:4.2f}KiB/s]".format(kibs * cratio) if compression_support else "", self.protocol.errors), end='')
dump_pctg += 0.1 dump_pctg += 0.1
print("\r{0:2.2f}% {1:4.2f}KiB/s {2} Errors: {3}".format(100, kibs, "[{0:4.2f}KiB/s]".format(kibs * cratio) if compression_support else "", self.protocol.errors)) # no one likes transfers finishing at 99.8% if self.protocol.errors > 0:
# Dump last status (errors may not be visible)
print("\r{0:2.0f}% {1:4.2f}KiB/s {2} Errors: {3} - Aborting...".format((i / blocks) * 100, kibs, "[{0:4.2f}KiB/s]".format(kibs * cratio) if compression_support else "", self.protocol.errors), end='')
print("") # New line to break the transfer speed line
self.close()
print("Transfer aborted due to protocol errors")
#raise Exception("Transfer aborted due to protocol errors")
return False;
print("\r{0:2.0f}% {1:4.2f}KiB/s {2} Errors: {3}".format(100, kibs, "[{0:4.2f}KiB/s]".format(kibs * cratio) if compression_support else "", self.protocol.errors)) # no one likes transfers finishing at 99.8%
self.close() if not self.close():
print("Transfer failed")
return False
print("Transfer complete") print("Transfer complete")
return True
class EchoProtocol(object): class EchoProtocol(object):

View File

@ -20,14 +20,18 @@ Import("env")
import MarlinBinaryProtocol import MarlinBinaryProtocol
# Internal debug flag
Debug = False
#-----------------# #-----------------#
# Upload Callback # # Upload Callback #
#-----------------# #-----------------#
def Upload(source, target, env): def Upload(source, target, env):
#-------#
# Debug #
#-------#
Debug = False # Set to True to enable script debug
def debugPrint(data):
if Debug: print(f"[Debug]: {data}")
#------------------# #------------------#
# Marlin functions # # Marlin functions #
#------------------# #------------------#
@ -39,19 +43,35 @@ def Upload(source, target, env):
# Port functions # # Port functions #
#----------------# #----------------#
def _GetUploadPort(env): def _GetUploadPort(env):
if Debug: print('Autodetecting upload port...') debugPrint('Autodetecting upload port...')
env.AutodetectUploadPort(env) env.AutodetectUploadPort(env)
port = env.subst('$UPLOAD_PORT') portName = env.subst('$UPLOAD_PORT')
if not port: if not portName:
raise Exception('Error detecting the upload port.') raise Exception('Error detecting the upload port.')
if Debug: print('OK') debugPrint('OK')
return port return portName
#-------------------------# #-------------------------#
# Simple serial functions # # Simple serial functions #
#-------------------------# #-------------------------#
def _OpenPort():
# Open serial port
if port.is_open: return
debugPrint('Opening upload port...')
port.open()
port.reset_input_buffer()
debugPrint('OK')
def _ClosePort():
# Open serial port
if port is None: return
if not port.is_open: return
debugPrint('Closing upload port...')
port.close()
debugPrint('OK')
def _Send(data): def _Send(data):
if Debug: print(f'>> {data}') debugPrint(f'>> {data}')
strdata = bytearray(data, 'utf8') + b'\n' strdata = bytearray(data, 'utf8') + b'\n'
port.write(strdata) port.write(strdata)
time.sleep(0.010) time.sleep(0.010)
@ -60,37 +80,37 @@ def Upload(source, target, env):
clean_responses = [] clean_responses = []
responses = port.readlines() responses = port.readlines()
for Resp in responses: for Resp in responses:
# Test: suppress invaid chars (coming from debug info) # Suppress invalid chars (coming from debug info)
try: try:
clean_response = Resp.decode('utf8').rstrip().lstrip() clean_response = Resp.decode('utf8').rstrip().lstrip()
clean_responses.append(clean_response) clean_responses.append(clean_response)
debugPrint(f'<< {clean_response}')
except: except:
pass pass
if Debug: print(f'<< {clean_response}')
return clean_responses return clean_responses
#------------------# #------------------#
# SDCard functions # # SDCard functions #
#------------------# #------------------#
def _CheckSDCard(): def _CheckSDCard():
if Debug: print('Checking SD card...') debugPrint('Checking SD card...')
_Send('M21') _Send('M21')
Responses = _Recv() Responses = _Recv()
if len(Responses) < 1 or not any('SD card ok' in r for r in Responses): if len(Responses) < 1 or not any('SD card ok' in r for r in Responses):
raise Exception('Error accessing SD card') raise Exception('Error accessing SD card')
if Debug: print('SD Card OK') debugPrint('SD Card OK')
return True return True
#----------------# #----------------#
# File functions # # File functions #
#----------------# #----------------#
def _GetFirmwareFiles(UseLongFilenames): def _GetFirmwareFiles(UseLongFilenames):
if Debug: print('Get firmware files...') debugPrint('Get firmware files...')
_Send(f"M20 F{'L' if UseLongFilenames else ''}") _Send(f"M20 F{'L' if UseLongFilenames else ''}")
Responses = _Recv() Responses = _Recv()
if len(Responses) < 3 or not any('file list' in r for r in Responses): if len(Responses) < 3 or not any('file list' in r for r in Responses):
raise Exception('Error getting firmware files') raise Exception('Error getting firmware files')
if Debug: print('OK') debugPrint('OK')
return Responses return Responses
def _FilterFirmwareFiles(FirmwareList, UseLongFilenames): def _FilterFirmwareFiles(FirmwareList, UseLongFilenames):
@ -114,6 +134,17 @@ def Upload(source, target, env):
raise Exception(f"Firmware file '{FirmwareFile}' not removed") raise Exception(f"Firmware file '{FirmwareFile}' not removed")
return Removed return Removed
def _RollbackUpload(FirmwareFile):
if not rollback: return
print(f"Rollback: trying to delete firmware '{FirmwareFile}'...")
_OpenPort()
# Wait for SD card release
time.sleep(1)
# Remount SD card
_CheckSDCard()
print(' OK' if _RemoveFirmwareFile(FirmwareFile) else ' Error!')
_ClosePort()
#---------------------# #---------------------#
# Callback Entrypoint # # Callback Entrypoint #
@ -121,6 +152,7 @@ def Upload(source, target, env):
port = None port = None
protocol = None protocol = None
filetransfer = None filetransfer = None
rollback = False
# Get Marlin evironment vars # Get Marlin evironment vars
MarlinEnv = env['MARLIN_FEATURES'] MarlinEnv = env['MARLIN_FEATURES']
@ -204,9 +236,9 @@ def Upload(source, target, env):
if not marlin_custom_firmware_upload: if not marlin_custom_firmware_upload:
raise Exception(f"CUSTOM_FIRMWARE_UPLOAD must be enabled in 'Configuration_adv.h' for '{marlin_motherboard}'") raise Exception(f"CUSTOM_FIRMWARE_UPLOAD must be enabled in 'Configuration_adv.h' for '{marlin_motherboard}'")
# Init serial port # Init & Open serial port
port = serial.Serial(upload_port, baudrate = upload_speed, write_timeout = 0, timeout = 0.1) port = serial.Serial(upload_port, baudrate = upload_speed, write_timeout = 0, timeout = 0.1)
port.reset_input_buffer() _OpenPort()
# Check SD card status # Check SD card status
_CheckSDCard() _CheckSDCard()
@ -228,24 +260,26 @@ def Upload(source, target, env):
print(' OK' if _RemoveFirmwareFile(OldFirmwareFile) else ' Error!') print(' OK' if _RemoveFirmwareFile(OldFirmwareFile) else ' Error!')
# Close serial # Close serial
port.close() _ClosePort()
# Cleanup completed # Cleanup completed
if Debug: print('Cleanup completed') debugPrint('Cleanup completed')
# WARNING! The serial port must be closed here because the serial transfer that follow needs it! # WARNING! The serial port must be closed here because the serial transfer that follow needs it!
# Upload firmware file # Upload firmware file
if Debug: print(f"Copy '{upload_firmware_source_name}' --> '{upload_firmware_target_name}'") debugPrint(f"Copy '{upload_firmware_source_name}' --> '{upload_firmware_target_name}'")
protocol = MarlinBinaryProtocol.Protocol(upload_port, upload_speed, upload_blocksize, float(upload_error_ratio), int(upload_timeout)) protocol = MarlinBinaryProtocol.Protocol(upload_port, upload_speed, upload_blocksize, float(upload_error_ratio), int(upload_timeout))
#echologger = MarlinBinaryProtocol.EchoProtocol(protocol) #echologger = MarlinBinaryProtocol.EchoProtocol(protocol)
protocol.connect() protocol.connect()
# Mark the rollback (delete broken transfer) from this point on
rollback = True
filetransfer = MarlinBinaryProtocol.FileTransferProtocol(protocol) filetransfer = MarlinBinaryProtocol.FileTransferProtocol(protocol)
filetransfer.copy(upload_firmware_source_name, upload_firmware_target_name, upload_compression, upload_test) transferOK = filetransfer.copy(upload_firmware_source_name, upload_firmware_target_name, upload_compression, upload_test)
protocol.disconnect() protocol.disconnect()
# Notify upload completed # Notify upload completed
protocol.send_ascii('M117 Firmware uploaded') protocol.send_ascii('M117 Firmware uploaded' if transferOK else 'M117 Firmware upload failed')
# Remount SD card # Remount SD card
print('Wait for SD card release...') print('Wait for SD card release...')
@ -253,34 +287,56 @@ def Upload(source, target, env):
print('Remount SD card') print('Remount SD card')
protocol.send_ascii('M21') protocol.send_ascii('M21')
# Trigger firmware update # Transfer failed?
if upload_reset: if not transferOK:
print('Trigger firmware update...') protocol.shutdown()
protocol.send_ascii('M997', True) _RollbackUpload(upload_firmware_target_name)
else:
# Trigger firmware update
if upload_reset:
print('Trigger firmware update...')
protocol.send_ascii('M997', True)
protocol.shutdown()
protocol.shutdown() print('Firmware update completed' if transferOK else 'Firmware update failed')
print('Firmware update completed') return 0 if transferOK else -1
except KeyboardInterrupt: except KeyboardInterrupt:
if port: port.close() print('Aborted by user')
if filetransfer: filetransfer.abort() if filetransfer: filetransfer.abort()
if protocol: protocol.shutdown() if protocol:
protocol.disconnect()
protocol.shutdown()
_RollbackUpload(upload_firmware_target_name)
_ClosePort()
raise raise
except serial.SerialException as se: except serial.SerialException as se:
if port: port.close() # This exception is raised only for send_ascii data (not for binary transfer)
print(f'Serial excepion: {se}') print(f'Serial excepion: {se}, transfer aborted')
if protocol:
protocol.disconnect()
protocol.shutdown()
_RollbackUpload(upload_firmware_target_name)
_ClosePort()
raise Exception(se) raise Exception(se)
except MarlinBinaryProtocol.FatalError: except MarlinBinaryProtocol.FatalError:
if port: port.close() print('Too many retries, transfer aborted')
if protocol: protocol.shutdown() if protocol:
print('Too many retries, Abort') protocol.disconnect()
protocol.shutdown()
_RollbackUpload(upload_firmware_target_name)
_ClosePort()
raise raise
except: except Exception as ex:
if port: port.close() print(f"\nException: {ex}, transfer aborted")
if protocol: protocol.shutdown() if protocol:
protocol.disconnect()
protocol.shutdown()
_RollbackUpload(upload_firmware_target_name)
_ClosePort()
print('Firmware not updated') print('Firmware not updated')
raise raise