admin.py 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268
  1. #!/usr/bin/env python3
  2. """
  3. # CNC-remote-control
  4. # Admin tool
  5. #
  6. # Copyright (C) 2011-2023 Michael Büsch <m@bues.ch>
  7. #
  8. # This program is free software; you can redistribute it and/or
  9. # modify it under the terms of the GNU General Public License
  10. # version 2 as published by the Free Software Foundation.
  11. #
  12. # This program is distributed in the hope that it will be useful,
  13. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. # GNU General Public License for more details.
  16. """
  17. import sys
  18. import time
  19. import getopt
  20. from cnccontrol_driver import *
  21. # Hardware constants
  22. CPU_BOOT_OFFSET = 0x7000
  23. CPU_APP_SIZE = CPU_BOOT_OFFSET
  24. CPU_PAGE_SIZE = 0x80
  25. COPROC_BOOT_OFFSET = 0x1800
  26. COPROC_PAGE_SIZE = 0x40
  27. COPROC_APP_SIZE = COPROC_BOOT_OFFSET
  28. def arg2bool(arg):
  29. arg = arg.lower()
  30. if arg in ("yes", "true"):
  31. return True
  32. if arg in ("no", "false"):
  33. return False
  34. try:
  35. return bool(int(arg))
  36. except ValueError:
  37. pass
  38. return False
  39. class IHEXParser(object):
  40. TYPE_DATA = 0
  41. TYPE_EOF = 1
  42. TYPE_ESAR = 2
  43. TYPE_SSAR = 3
  44. TYPE_ELAR = 4
  45. TYPE_SLAR = 5
  46. def __init__(self, ihexfile, imagesize):
  47. image = [0xFF] * imagesize
  48. try:
  49. lines = file(ihexfile, "rb").readlines()
  50. hiAddr = 0
  51. for line in lines:
  52. line = line.strip()
  53. if not line:
  54. continue
  55. if len(line) < 11 or (len(line) - 1) % 2 != 0:
  56. raise CNCCException("Invalid ihex file format (length error)")
  57. if line[0] != ':':
  58. raise CNCCException("Invalid ihex file format (magic error)")
  59. count = int(line[1:3], 16)
  60. if len(line) != count * 2 + 11:
  61. raise CNCCException("Invalid ihex file format (count error)")
  62. addr = (int(line[3:5], 16) << 8) | int(line[5:7], 16)
  63. addr |= hiAddr << 16
  64. recordType = int(line[7:9], 16)
  65. checksum = 0
  66. for i in range(1, len(line), 2):
  67. b = int(line[i:i+2], 16)
  68. checksum = (checksum + b) & 0xFF
  69. if checksum != 0:
  70. raise CNCCException("Invalid ihex file format (checksum error)")
  71. if recordType == self.TYPE_EOF:
  72. break
  73. if recordType == self.TYPE_ELAR:
  74. if count != 2:
  75. raise CNCCException("Invalid ihex file format (inval ELAR)")
  76. hiAddr = (int(line[9:11], 16) << 8) | int(line[11:13], 16)
  77. continue
  78. if recordType == self.TYPE_DATA:
  79. if len(image) < addr + count:
  80. raise CNCCException("Ihex data outside of image bounds")
  81. for i in range(9, 9 + count * 2, 2):
  82. image[(i - 9) / 2 + addr] = int(line[i:i+2], 16)
  83. continue
  84. raise CNCCException("Invalid ihex file format (unsup type %d)" % recordType)
  85. except (ValueError) as e:
  86. raise CNCCException("Invalid ihex file format (digit format)")
  87. except (IOError) as e:
  88. raise CNCCException("Failed to read file %s: %s" % (ihexfile, str(e)))
  89. self.image = image
  90. class Context(object):
  91. def __init__(self):
  92. self.cncc = None
  93. def getCNCC(self):
  94. if not self.cncc:
  95. self.cncc = CNCControl()
  96. if not self.cncc.probe():
  97. raise CNCCException("Did not find CNC "
  98. "Control device on USB bus")
  99. return self.cncc
  100. def handle_cpu_context(context, arg):
  101. cncc = context.getCNCC()
  102. if cncc.deviceRunsBootloader():
  103. print("The CPU is running in BOOTLOADER code")
  104. else:
  105. print("The CPU is running in APPLICATION code")
  106. def handle_verbose_debug(context, enable):
  107. enable = arg2bool(enable)
  108. cncc = context.getCNCC()
  109. devFlagsSet = 0
  110. if enable:
  111. print("Enabling verbose debug mode...")
  112. devFlagsSet = ControlMsgDevflags.DEVICE_FLG_VERBOSEDBG
  113. else:
  114. print("Disabling verbose debug mode...")
  115. msg = ControlMsgDevflags(ControlMsgDevflags.DEVICE_FLG_VERBOSEDBG,
  116. devFlagsSet)
  117. reply = cncc.controlMsgSyncReply(msg)
  118. if not reply.isOK():
  119. raise CNCCException("Failed to set device flags: %s" % str(reply))
  120. print("Device flags:", reply)
  121. def handle_enterboot(context, arg):
  122. print("Entering bootloader...")
  123. cncc = context.getCNCC()
  124. if not cncc.deviceRunsBootloader():
  125. # Enter CPU bootloader
  126. msg = ControlMsgEnterboot(ControlMsg.TARGET_CPU)
  127. cncc.controlMsg(msg)
  128. time.sleep(0.3)
  129. if not cncc.reconnect():
  130. raise CNCCException("Failed to enter CPU bootloader. "
  131. "The USB device did not reconnect.")
  132. # Ping the bootloader
  133. ping = ControlMsgPing(hdrFlags=ControlMsg.CONTROL_FLG_BOOTLOADER)
  134. reply = cncc.controlMsgSyncReply(ping)
  135. if not reply.isOK():
  136. raise CNCCException("Failed to ping the CPU bootloader")
  137. # Enter coprocessor bootloader
  138. msg = ControlMsgEnterboot(ControlMsg.TARGET_COPROC,
  139. hdrFlags=ControlMsg.CONTROL_FLG_BOOTLOADER)
  140. reply = cncc.controlMsgSyncReply(msg, timeoutMs=1000)
  141. if not reply.isOK():
  142. raise CNCCException("Failed to enter coprocessor bootloader: %s" % str(reply))
  143. def handle_exitboot(context, arg):
  144. print("Exiting bootloader...")
  145. cncc = context.getCNCC()
  146. if not cncc.deviceRunsBootloader():
  147. return
  148. # Exit coprocessor bootloader
  149. msg = ControlMsgExitboot(ControlMsg.TARGET_COPROC)
  150. reply = cncc.controlMsgSyncReply(msg, timeoutMs=1000)
  151. if not reply.isOK():
  152. print("Failed to exit coprocessor bootloader: %s" % str(reply))
  153. # Exit CPU bootloader
  154. msg = ControlMsgExitboot(ControlMsg.TARGET_CPU)
  155. cncc.controlMsg(msg)
  156. time.sleep(0.3)
  157. if not cncc.reconnect():
  158. raise CNCCException("Failed to exit CPU bootloader. "
  159. "The USB device did not reconnect.")
  160. def __flashImage(context, ihexfile, offset, size, pageSize, targetMCU):
  161. cncc = context.getCNCC()
  162. p = IHEXParser(ihexfile, size)
  163. for pageAddr in range(offset, size, pageSize):
  164. page = p.image[pageAddr:pageAddr+min(pageSize, size - pageAddr)]
  165. for chunkOffset in range(0, len(page), ControlMsgBootWritebuf.DATA_MAX_BYTES):
  166. chunkSize = min(ControlMsgBootWritebuf.DATA_MAX_BYTES,
  167. len(page) - chunkOffset)
  168. chunk = page[chunkOffset:chunkOffset+chunkSize]
  169. msg = ControlMsgBootWritebuf(chunkOffset, chunk)
  170. reply = cncc.controlMsgSyncReply(msg)
  171. if not reply.isOK():
  172. raise CNCCException("Failed to write image data to "
  173. "flash buffer: %s" % str(reply))
  174. msg = ControlMsgBootFlashpg(pageAddr, targetMCU)
  175. reply = cncc.controlMsgSyncReply(msg, timeoutMs=2500)
  176. if not reply.isOK():
  177. raise CNCCException("Failed to flash page: %s" % str(reply))
  178. def handle_flash_cpu(context, ihexfile):
  179. print("Flashing CPU image")
  180. __flashImage(context, ihexfile, 0, CPU_APP_SIZE, CPU_PAGE_SIZE,
  181. ControlMsg.TARGET_CPU)
  182. def handle_flash_coprocessor(context, ihexfile):
  183. print("Flashing coprocessor image")
  184. __flashImage(context, ihexfile, 0, COPROC_APP_SIZE, COPROC_PAGE_SIZE,
  185. ControlMsg.TARGET_COPROC)
  186. def usage():
  187. print("admin.py [OPTIONS]")
  188. print("")
  189. print(" -c|--cpu-context Find out the CPU context (boot or app)")
  190. print("")
  191. print(" -V|--verbose-debug BOOL Enable/disable verbose debugging messages.")
  192. print("")
  193. print(" -b|--enterboot Enter the CPU and coproc bootloader")
  194. print(" -x|--exitboot Exit the CPU and coproc bootloader")
  195. print(" -f|--flash-cpu IHEX Flash an ihex file to the CPU")
  196. print(" -F|--flash-coproc IHEX Flash an ihex file to the coprocessor")
  197. def main():
  198. actions = []
  199. try:
  200. (opts, args) = getopt.getopt(sys.argv[1:],
  201. "hcV:bxf:F:",
  202. [ "help", "cpu-context", "verbose-debug=", "enterboot", "exitboot",
  203. "flash-cpu=", "flash-coproc=", ])
  204. except getopt.GetoptError:
  205. usage()
  206. return 1
  207. for (o, v) in opts:
  208. if o in ("-h", "--help"):
  209. usage()
  210. return 0
  211. if o in ("-c", "--cpu-context"):
  212. actions.append( ["cpu-context", v] )
  213. if o in ("-V", "--verbose-debug"):
  214. actions.append( ["verbose-debug", v] )
  215. if o in ("-b", "--enterboot"):
  216. actions.append( ["enterboot", v] )
  217. if o in ("-x", "--exitboot"):
  218. actions.append( ["exitboot", v] )
  219. if o in ("-f", "--flash-cpu"):
  220. actions.append( ["flash-cpu", v] )
  221. if o in ("-F", "--flash-coproc"):
  222. actions.append( ["flash-coproc", v] )
  223. handlers = {
  224. "cpu-context" : handle_cpu_context,
  225. "verbose-debug" : handle_verbose_debug,
  226. "enterboot" : handle_enterboot,
  227. "exitboot" : handle_exitboot,
  228. "flash-cpu" : handle_flash_cpu,
  229. "flash-coproc" : handle_flash_coprocessor,
  230. }
  231. try:
  232. if not actions:
  233. print("No action specified")
  234. return 1
  235. context = Context()
  236. for action in actions:
  237. handler = handlers[action[0]]
  238. handler(context, action[1])
  239. except (CNCCException) as e:
  240. print("CNC Control exception: %s" % str(e))
  241. return 1
  242. return 0
  243. if __name__ == "__main__":
  244. sys.exit(main())