PageRenderTime 62ms CodeModel.GetById 32ms RepoModel.GetById 0ms app.codeStats 0ms

/pmvm/platform/pic24/tools/ipm.py

https://github.com/leaflabs/projects
Python | 430 lines | 387 code | 7 blank | 36 comment | 1 complexity | 4a883cf05f27454ae93a4f1699df1aa2 MD5 | raw file
  1. #!/usr/bin/env python
  2. # This file is Copyright 2007, 2009 Dean Hall.
  3. #
  4. # This file is part of the Python-on-a-Chip program.
  5. # Python-on-a-Chip is free software: you can redistribute it and/or modify
  6. # it under the terms of the GNU LESSER GENERAL PUBLIC LICENSE Version 2.1.
  7. #
  8. # Python-on-a-Chip is distributed in the hope that it will be useful,
  9. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  11. # A copy of the GNU LESSER GENERAL PUBLIC LICENSE Version 2.1
  12. # is seen in the file COPYING in this directory.
  13. """
  14. ==================
  15. Interactive PyMite
  16. ==================
  17. An interactive command line that runs on a host computer that is connected
  18. to a target device that is running PyMite. The host computer compiles the
  19. interactive statement and converts it to a form that PyMite can handle,
  20. sends that over the connection where the target device loads and interprets it.
  21. The target device then packages any result, sends it to the host computer
  22. and the host computer prints the result.
  23. """
  24. ## @file
  25. # @copybrief ipm
  26. ## @package ipm
  27. # @brief Interactive PyMite
  28. #
  29. #
  30. # An interactive command line that runs on a host computer that is connected
  31. # to a target device that is running PyMite. The host computer compiles the
  32. # interactive statement and converts it to a form that PyMite can handle,
  33. # sends that over the connection where the target device loads and interprets it.
  34. # The target device then packages any result, sends it to the host computer
  35. # and the host computer prints the result.
  36. import cmd, dis, getopt, os, subprocess, sys, time
  37. import pmImgCreator
  38. __usage__ = """USAGE:
  39. ipm.py -[d|s /dev/tty] --[desktop | serial=/dev/tty [baud=19200]]
  40. -h Prints this usage message.
  41. --help
  42. -d Specifies a desktop connection; uses pipes to send/receive bytes
  43. --desktop to/from the target, which is the vm also running on the desktop.
  44. ipm spawns the vm and runs ipm-desktop as a subprocess.
  45. -s <port> [<baud>] Specifies the port (device) for a serial connection.
  46. <port> resembles `com5` on Win32 or `/dev/cu.usbmodem1912`.
  47. Optional argument, <baud>, defaults to 19200.
  48. --serial=<port> Specifies the port (device) for a serial connection.
  49. --baud=<baud> Specifies the baud rate for a serial connection.
  50. REQUIREMENTS:
  51. - pySerial package from http://pyserial.sourceforge.net/
  52. """
  53. NEED_PYSERIAL = "Install the pySerial module from http://pyserial.sourceforge.net/"
  54. if not sys.platform.lower().startswith("win"):
  55. PMVM_EXE = "../platform/desktop/main.out"
  56. else:
  57. PMVM_EXE = "../platform/windows/Debug/pymite.exe"
  58. IPM_PROMPT = "ipm> "
  59. COMPILE_FN = "<ipm>"
  60. COMPILE_MODE = "single"
  61. INIT_MESSAGE = """Python-on-a-Chip is Copyright 2003, 2006, 2007, 2009 Dean Hall and others.
  62. Python-on-a-Chip is licensed under the GNU LESSER GENERAL PUBLIC LICENSE V 2.1
  63. PyMite is Copyright 2003, 2006, 2007, 2009 Dean Hall.
  64. PyMite is licensed under the GNU GENERAL PUBLIC LICENSE V 2.
  65. This software is offered with NO WARRANTY. See LICENSE for details.
  66. """
  67. HELP_MESSAGE = """Type the Python code that you want to run on the target device.
  68. If you see no prompt, type two consecutive returns to exit multiline mode.
  69. Type Ctrl+C to interrupt and Ctrl+D to quit (or Ctrl+Z <enter> on Win32).
  70. """
  71. REPLY_TERMINATOR = '\x04'
  72. if sys.platform.lower().startswith("win"):
  73. EOF_KEY = 'Z'
  74. else:
  75. EOF_KEY = 'D'
  76. class Connection(object):
  77. def open(self,): raise NotImplementedError
  78. def read(self,): raise NotImplementedError
  79. def write(self, msg): raise NotImplementedError
  80. def close(self,): raise NotImplementedError
  81. class PipeConnection(Connection):
  82. """Provides ipm-host to target connection over stdio pipes on the desktop.
  83. This connection should work on any POSIX-compliant OS.
  84. The ipm-device must be spawned as a subprocess
  85. (the executable created when PyMite was built with PLATFORM=desktop).
  86. """
  87. def __init__(self, target=PMVM_EXE):
  88. self.open(target)
  89. def open(self, target):
  90. self.child = subprocess.Popen(target,
  91. bufsize=-1,
  92. stdin=subprocess.PIPE,
  93. stdout=subprocess.PIPE,
  94. stderr=subprocess.PIPE,
  95. )
  96. def read(self,):
  97. # If the child process is not alive, read in everything from the buffer.
  98. # It will usually be an exception message from the target
  99. # TODO
  100. # Collect all characters up to and including the ipm reply terminator
  101. chars = []
  102. c = ''
  103. while c != REPLY_TERMINATOR:
  104. c = self.child.stdout.read(1)
  105. if c == '':
  106. # DEBUG: uncomment the next line to print the child's return val
  107. #print "DEBUG: child returncode = %s\n" % hex(self.child.poll())
  108. break
  109. chars.append(c)
  110. msg = "".join(chars)
  111. return msg
  112. def write(self, msg):
  113. self.child.stdin.write(msg)
  114. self.child.stdin.flush()
  115. def close(self,):
  116. self.write("\0")
  117. class SerialConnection(Connection):
  118. """Provides ipm-host to target connection over a serial device.
  119. This connection should work on any platform that PySerial supports.
  120. The ipm-device must be running at the same baud rate (19200 default).
  121. """
  122. def __init__(self, serdev="/dev/cu.SLAB_USBtoUART", baud=19200):
  123. try:
  124. import serial
  125. except Exception, e:
  126. print NEED_PYSERIAL
  127. raise e
  128. self.s = serial.Serial(serdev, baud)
  129. self.s.setDTR(False)
  130. self.s.setRTS(False)
  131. self.s.setTimeout(1)
  132. time.sleep(0.1)
  133. def read(self,):
  134. # Collect all characters up to and including the ipm reply terminator
  135. return self.s.readline() #eol=REPLY_TERMINATOR)
  136. def write(self, msg):
  137. self.s.write(msg)
  138. self.s.flush()
  139. def close(self,):
  140. self.s.close()
  141. class Interactive(cmd.Cmd):
  142. """The interactive command line parser accepts typed input line-by-line.
  143. If a statement requires multiple lines to complete, the input
  144. is collected until two sequential end-of-line characters are received.
  145. """
  146. ipmcommands = ("?", "help", "load",)
  147. def __init__(self, conn):
  148. cmd.Cmd.__init__(self,)
  149. self.prompt = IPM_PROMPT
  150. self.conn = conn
  151. self.pic = pmImgCreator.PmImgCreator()
  152. def do_help(self, *args):
  153. """Prints the help message.
  154. """
  155. self.stdout.write(HELP_MESSAGE)
  156. def do_load(self, *args):
  157. """Loads a module from the host to the target device.
  158. """
  159. # Ensure the filename arg names a python source file
  160. fn = args[0]
  161. if not os.path.exists(fn):
  162. self.stdout.write('File "%s" does not exist in %s.\n'
  163. % (fn, os.getcwd()))
  164. return
  165. if not fn.endswith(".py"):
  166. self.stdout.write('Error using "load <module>": '
  167. 'module must be a ".py" source file.\n')
  168. return
  169. src = open(fn).read()
  170. code = compile(src, fn, "exec")
  171. img = self.pic.co_to_str(code)
  172. self.conn.write(img)
  173. while True:
  174. str = self.conn.read()
  175. self.stdout.write(str)
  176. if len(str) == 0:
  177. break
  178. def onecmd(self, line):
  179. """Gathers one interactive line of input (gets more lines as needed).
  180. """
  181. # Ignore empty line, continue interactive prompt
  182. if not line:
  183. return
  184. # Handle ctrl+D (End Of File) input, stop interactive prompt
  185. if line == "EOF":
  186. self.conn.close()
  187. # Do this so OS prompt is on a new line
  188. self.stdout.write("\n")
  189. # Quit the run loop
  190. self.stop = True
  191. return True
  192. # Handle ipm-specific commands
  193. if line.split()[0] in Interactive.ipmcommands:
  194. cmd.Cmd.onecmd(self, line)
  195. return
  196. # Gather input from the interactive line
  197. codeobj = None
  198. while not codeobj:
  199. # Try to compile the given line
  200. try:
  201. codeobj = compile(line, COMPILE_FN, COMPILE_MODE)
  202. # Get more input if syntax error reports unexpected end of file
  203. except SyntaxError, se:
  204. # Print any other syntax error
  205. if not se.msg.startswith("unexpected EOF while parsing"):
  206. self.stdout.write("%s:%s\n" % (se.__class__.__name__, se))
  207. return
  208. # Restore the newline chopped by cmd.py:140
  209. line += "\n"
  210. # Get more input if needed
  211. while not line.endswith("\n\n"):
  212. line += self.stdin.readline()
  213. # Print any other exception
  214. except Exception, e:
  215. self.stdout.write("%s:%s\n" % (e.__class__.__name__, e))
  216. return
  217. # DEBUG: Uncomment the next line to print the statement's bytecodes
  218. #dis.disco(codeobj)
  219. # Convert to a code image
  220. try:
  221. codeimg = self.pic.co_to_str(codeobj)
  222. # Print any conversion errors
  223. except Exception, e:
  224. self.stdout.write("%s:%s\n" % (e.__class__.__name__, e))
  225. # Otherwise send the image and print the reply
  226. else:
  227. # DEBUG: Uncomment the next line to print the size of the code image
  228. # print "DEBUG: len(codeimg) = ", len(codeimg)
  229. # DEBUG: Uncomment the next line to print the code image
  230. # print "DEBUG: codeimg = ", repr(codeimg)
  231. try:
  232. self.conn.write(codeimg)
  233. except Exception, e:
  234. self.stdout.write(
  235. "Connection write error, type Ctrl+%s to quit.\n" % EOF_KEY)
  236. rv = self.conn.read()
  237. if rv == '':
  238. self.stdout.write(
  239. "Connection read error, type Ctrl+%s to quit.\n" % EOF_KEY)
  240. else:
  241. if rv.endswith(REPLY_TERMINATOR):
  242. self.stdout.write(rv[:-1])
  243. else:
  244. self.stdout.write(rv)
  245. def run(self,):
  246. """Runs the command loop and handles keyboard interrupts (ctrl+C).
  247. The command loop is what calls self.onecmd().
  248. """
  249. print INIT_MESSAGE,
  250. print HELP_MESSAGE,
  251. self.stop = False
  252. while not self.stop:
  253. try:
  254. self.cmdloop()
  255. except KeyboardInterrupt, ki:
  256. print "\n", ki.__class__.__name__
  257. # TODO: check connection?
  258. def parse_cmdline():
  259. """Parses the command line for options.
  260. """
  261. baud = 19200
  262. Conn = PipeConnection
  263. serdev = None
  264. try:
  265. opts, args = getopt.getopt(sys.argv[1:], "dhs",
  266. ["desktop", "help", "serial=", "baud="])
  267. except Exception, e:
  268. print __usage__
  269. sys.exit()
  270. if not opts:
  271. print __usage__
  272. sys.exit()
  273. for opt in opts:
  274. if opt[0] == "-d" or opt[0] == "--desktop":
  275. Conn = PipeConnection
  276. elif opt[0] == "-s":
  277. Conn = SerialConnection
  278. serdev = args[0]
  279. if len(args) > 1:
  280. baud = int(args[1])
  281. elif opt[0] == "--serial":
  282. Conn = SerialConnection
  283. serdev = opt[1]
  284. elif opt[0] == "--baud":
  285. assert serdev, "--serial must be specified before --baud."
  286. baud = int(opt[1])
  287. else:
  288. print __usage__
  289. sys.exit(0)
  290. if Conn == SerialConnection:
  291. c = Conn(serdev, baud)
  292. else:
  293. c = Conn()
  294. return c
  295. def main():
  296. conn = parse_cmdline()
  297. i = Interactive(conn)
  298. while True:
  299. i.do_load("robot.py")
  300. # Prompt for enter
  301. sys.stdout.write("Press enter to re-run your program...")
  302. sys.stdin.readline()
  303. # Reset PIC
  304. conn.s.setDTR(True)
  305. conn.s.setRTS(True)
  306. time.sleep(0.1)
  307. conn.s.setDTR(False)
  308. conn.s.setRTS(False)
  309. time.sleep(0.1)
  310. #i.run()
  311. def ser_test():
  312. """Test ipm over serial connection directly.
  313. """
  314. try:
  315. import serial
  316. except Exception, e:
  317. print NEED_PYSERIAL
  318. raise e
  319. pic = pmImgCreator.PmImgCreator()
  320. serconn = serial.Serial("/dev/cu.SLAB_USBtoUART", 19200)
  321. serconn.setTimeout(2)
  322. testcode = (
  323. 'print "Hello"\n',
  324. 'import sys\n',
  325. 'print sys.heap()\n',
  326. )
  327. for line in testcode:
  328. print "compiling ``%s``" % line
  329. codeobj = compile(line, COMPILE_FN, COMPILE_MODE)
  330. codeimg = pic.co_to_str(codeobj)
  331. print "codeimg is %d bytes" % len(codeimg)
  332. print "sending codeimg..."
  333. serconn.write(codeimg)
  334. reply = serconn.readline(eol=REPLY_TERMINATOR)
  335. print "reply is %d bytes" % len(reply)
  336. print "reply is:\n%s" % reply
  337. if __name__ == "__main__":
  338. main()