upload.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274
  1. import argparse
  2. import sys
  3. import os
  4. import time
  5. import random
  6. import serial
  7. Import("env")
  8. # Needed (only) for compression, but there are problems with pip install heatshrink
  9. #try:
  10. # import heatshrink
  11. #except ImportError:
  12. # # Install heatshrink
  13. # print("Installing 'heatshrink' python module...")
  14. # env.Execute(env.subst("$PYTHONEXE -m pip install heatshrink"))
  15. #
  16. # Not tested: If it's safe to install python libraries in PIO python try:
  17. # env.Execute(env.subst("$PYTHONEXE -m pip install https://github.com/p3p/pyheatshrink/releases/download/0.3.3/pyheatshrink-pip.zip"))
  18. import MarlinBinaryProtocol
  19. # Internal debug flag
  20. Debug = False
  21. #-----------------#
  22. # Upload Callback #
  23. #-----------------#
  24. def Upload(source, target, env):
  25. #------------------#
  26. # Marlin functions #
  27. #------------------#
  28. def _GetMarlinEnv(marlinEnv, feature):
  29. if not marlinEnv: return None
  30. return marlinEnv[feature] if feature in marlinEnv else None
  31. #----------------#
  32. # Port functions #
  33. #----------------#
  34. def _GetUploadPort(env):
  35. if Debug: print('Autodetecting upload port...')
  36. env.AutodetectUploadPort(env)
  37. port = env.subst('$UPLOAD_PORT')
  38. if not port:
  39. raise Exception('Error detecting the upload port.')
  40. if Debug: print('OK')
  41. return port
  42. #-------------------------#
  43. # Simple serial functions #
  44. #-------------------------#
  45. def _Send(data):
  46. if Debug: print(f'>> {data}')
  47. strdata = bytearray(data, 'utf8') + b'\n'
  48. port.write(strdata)
  49. time.sleep(0.010)
  50. def _Recv():
  51. clean_responses = []
  52. responses = port.readlines()
  53. for Resp in responses:
  54. # Test: suppress invaid chars (coming from debug info)
  55. try:
  56. clean_response = Resp.decode('utf8').rstrip().lstrip()
  57. clean_responses.append(clean_response)
  58. except:
  59. pass
  60. if Debug: print(f'<< {clean_response}')
  61. return clean_responses
  62. #------------------#
  63. # SDCard functions #
  64. #------------------#
  65. def _CheckSDCard():
  66. if Debug: print('Checking SD card...')
  67. _Send('M21')
  68. Responses = _Recv()
  69. if len(Responses) < 1 or not any('SD card ok' in r for r in Responses):
  70. raise Exception('Error accessing SD card')
  71. if Debug: print('SD Card OK')
  72. return True
  73. #----------------#
  74. # File functions #
  75. #----------------#
  76. def _GetFirmwareFiles():
  77. if Debug: print('Get firmware files...')
  78. _Send('M20 F')
  79. Responses = _Recv()
  80. if len(Responses) < 3 or not any('file list' in r for r in Responses):
  81. raise Exception('Error getting firmware files')
  82. if Debug: print('OK')
  83. return Responses
  84. def _FilterFirmwareFiles(FirmwareList):
  85. Firmwares = []
  86. for FWFile in FirmwareList:
  87. if not '/' in FWFile and '.BIN' in FWFile:
  88. idx = FWFile.index('.BIN')
  89. Firmwares.append(FWFile[:idx+4])
  90. return Firmwares
  91. def _RemoveFirmwareFile(FirmwareFile):
  92. _Send(f'M30 /{FirmwareFile}')
  93. Responses = _Recv()
  94. Removed = len(Responses) >= 1 and any('File deleted' in r for r in Responses)
  95. if not Removed:
  96. raise Exception(f"Firmware file '{FirmwareFile}' not removed")
  97. return Removed
  98. #---------------------#
  99. # Callback Entrypoint #
  100. #---------------------#
  101. port = None
  102. protocol = None
  103. filetransfer = None
  104. # Get Marlin evironment vars
  105. MarlinEnv = env['MARLIN_FEATURES']
  106. marlin_pioenv = _GetMarlinEnv(MarlinEnv, 'PIOENV')
  107. marlin_motherboard = _GetMarlinEnv(MarlinEnv, 'MOTHERBOARD')
  108. marlin_board_info_name = _GetMarlinEnv(MarlinEnv, 'BOARD_INFO_NAME')
  109. marlin_board_custom_build_flags = _GetMarlinEnv(MarlinEnv, 'BOARD_CUSTOM_BUILD_FLAGS')
  110. marlin_firmware_bin = _GetMarlinEnv(MarlinEnv, 'FIRMWARE_BIN')
  111. marlin_custom_firmware_upload = _GetMarlinEnv(MarlinEnv, 'CUSTOM_FIRMWARE_UPLOAD') is not None
  112. marlin_short_build_version = _GetMarlinEnv(MarlinEnv, 'SHORT_BUILD_VERSION')
  113. marlin_string_config_h_author = _GetMarlinEnv(MarlinEnv, 'STRING_CONFIG_H_AUTHOR')
  114. # Get firmware upload params
  115. upload_firmware_source_name = str(source[0]) # Source firmware filename
  116. upload_speed = env['UPLOAD_SPEED'] if 'UPLOAD_SPEED' in env else 115200
  117. # baud rate of serial connection
  118. upload_port = _GetUploadPort(env) # Serial port to use
  119. # Set local upload params
  120. upload_firmware_target_name = os.path.basename(upload_firmware_source_name) # WARNING! Need rework on "binary_stream" to allow filename > 8.3
  121. # Target firmware filename
  122. upload_timeout = 1000 # Communication timout, lossy/slow connections need higher values
  123. upload_blocksize = 512 # Transfer block size. 512 = Autodetect
  124. upload_compression = True # Enable compression
  125. upload_error_ratio = 0 # Simulated corruption ratio
  126. upload_test = False # Benchmark the serial link without storing the file
  127. upload_reset = True # Trigger a soft reset for firmware update after the upload
  128. # Set local upload params based on board type to change script behavior
  129. # "upload_delete_old_bins": delete all *.bin files in the root of SD Card
  130. upload_delete_old_bins = marlin_motherboard in ['BOARD_CREALITY_V4', 'BOARD_CREALITY_V4210', 'BOARD_CREALITY_V423', 'BOARD_CREALITY_V427',
  131. 'BOARD_CREALITY_V431', 'BOARD_CREALITY_V452', 'BOARD_CREALITY_V453', 'BOARD_CREALITY_V24S1']
  132. try:
  133. # Start upload job
  134. print(f"Uploading firmware '{os.path.basename(upload_firmware_target_name)}' to '{marlin_motherboard}' via '{upload_port}'")
  135. # Dump some debug info
  136. if Debug:
  137. print('Upload using:')
  138. print('---- Marlin --------------------')
  139. print(f' PIOENV : {marlin_pioenv}')
  140. print(f' SHORT_BUILD_VERSION : {marlin_short_build_version}')
  141. print(f' STRING_CONFIG_H_AUTHOR : {marlin_string_config_h_author}')
  142. print(f' MOTHERBOARD : {marlin_motherboard}')
  143. print(f' BOARD_INFO_NAME : {marlin_board_info_name}')
  144. print(f' CUSTOM_BUILD_FLAGS : {marlin_board_custom_build_flags}')
  145. print(f' FIRMWARE_BIN : {marlin_firmware_bin}')
  146. print(f' CUSTOM_FIRMWARE_UPLOAD : {marlin_custom_firmware_upload}')
  147. print('---- Upload parameters ---------')
  148. print(f' Source : {upload_firmware_source_name}')
  149. print(f' Target : {upload_firmware_target_name}')
  150. print(f' Port : {upload_port} @ {upload_speed} baudrate')
  151. print(f' Timeout : {upload_timeout}')
  152. print(f' Block size : {upload_blocksize}')
  153. print(f' Compression : {upload_compression}')
  154. print(f' Error ratio : {upload_error_ratio}')
  155. print(f' Test : {upload_test}')
  156. print(f' Reset : {upload_reset}')
  157. print('--------------------------------')
  158. # Custom implementations based on board parameters
  159. # Delete all *.bin files on the root of SD Card (if flagged)
  160. if upload_delete_old_bins:
  161. # CUSTOM_FIRMWARE_UPLOAD is needed for this feature
  162. if not marlin_custom_firmware_upload:
  163. raise Exception(f"CUSTOM_FIRMWARE_UPLOAD must be enabled in 'Configuration_adv.h' for '{marlin_motherboard}'")
  164. # Generate a new 8.3 random filename
  165. # This board remember the last firmware filename and doesn't allow to flash from that filename
  166. upload_firmware_target_name = f"fw-{''.join(random.choices('ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', k=5))}.BIN"
  167. print(f"Board {marlin_motherboard}: Overriding firmware filename to '{upload_firmware_target_name}'")
  168. # Init serial port
  169. port = serial.Serial(upload_port, baudrate = upload_speed, write_timeout = 0, timeout = 0.1)
  170. port.reset_input_buffer()
  171. # Check SD card status
  172. _CheckSDCard()
  173. # Get firmware files
  174. FirmwareFiles = _GetFirmwareFiles()
  175. if Debug:
  176. for FirmwareFile in FirmwareFiles:
  177. print(f'Found: {FirmwareFile}')
  178. # Get all 1st level firmware files (to remove)
  179. OldFirmwareFiles = _FilterFirmwareFiles(FirmwareFiles[1:len(FirmwareFiles)-2]) # Skip header and footers of list
  180. if len(OldFirmwareFiles) == 0:
  181. print('No old firmware files to delete')
  182. else:
  183. print(f"Remove {len(OldFirmwareFiles)} old firmware file{'s' if len(OldFirmwareFiles) != 1 else ''}:")
  184. for OldFirmwareFile in OldFirmwareFiles:
  185. print(f" -Removing- '{OldFirmwareFile}'...")
  186. print(' OK' if _RemoveFirmwareFile(OldFirmwareFile) else ' Error!')
  187. # Close serial
  188. port.close()
  189. # Cleanup completed
  190. if Debug: print('Cleanup completed')
  191. # WARNING! The serial port must be closed here because the serial transfer that follow needs it!
  192. # Upload firmware file
  193. if Debug: print(f"Copy '{upload_firmware_source_name}' --> '{upload_firmware_target_name}'")
  194. protocol = MarlinBinaryProtocol.Protocol(upload_port, upload_speed, upload_blocksize, float(upload_error_ratio), int(upload_timeout))
  195. #echologger = MarlinBinaryProtocol.EchoProtocol(protocol)
  196. protocol.connect()
  197. filetransfer = MarlinBinaryProtocol.FileTransferProtocol(protocol)
  198. filetransfer.copy(upload_firmware_source_name, upload_firmware_target_name, upload_compression, upload_test)
  199. protocol.disconnect()
  200. # Notify upload completed
  201. protocol.send_ascii('M117 Firmware uploaded')
  202. # Remount SD card
  203. print('Wait for SD card release...')
  204. time.sleep(1)
  205. print('Remount SD card')
  206. protocol.send_ascii('M21')
  207. # Trigger firmware update
  208. if upload_reset:
  209. print('Trigger firmware update...')
  210. protocol.send_ascii('M997', True)
  211. protocol: protocol.shutdown()
  212. print('Firmware update completed')
  213. except KeyboardInterrupt:
  214. if port: port.close()
  215. if filetransfer: filetransfer.abort()
  216. if protocol: protocol.shutdown()
  217. raise
  218. except serial.SerialException as se:
  219. if port: port.close()
  220. print(f'Serial excepion: {se}')
  221. raise Exception(se)
  222. except MarlinBinaryProtocol.FatalError:
  223. if port: port.close()
  224. if protocol: protocol.shutdown()
  225. print('Too many retries, Abort')
  226. raise
  227. except:
  228. if port: port.close()
  229. if protocol: protocol.shutdown()
  230. print('Firmware not updated')
  231. raise
  232. # Attach custom upload callback
  233. env.Replace(UPLOADCMD=Upload)