ipm.py

Go to the documentation of this file.
00001 #!/usr/bin/env python
00002 
00003 # This file is Copyright 2007, 2009 Dean Hall.
00004 #
00005 # This file is part of the Python-on-a-Chip program.
00006 # Python-on-a-Chip is free software: you can redistribute it and/or modify
00007 # it under the terms of the GNU LESSER GENERAL PUBLIC LICENSE Version 2.1.
00008 # 
00009 # Python-on-a-Chip is distributed in the hope that it will be useful,
00010 # but WITHOUT ANY WARRANTY; without even the implied warranty of
00011 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
00012 # A copy of the GNU LESSER GENERAL PUBLIC LICENSE Version 2.1
00013 # is seen in the file COPYING in this directory.
00014 
00015 """
00016 ==================
00017 Interactive PyMite
00018 ==================
00019 
00020 An interactive command line that runs on a host computer that is connected
00021 to a target device that is running PyMite.  The host computer compiles the
00022 interactive statement and converts it to a form that PyMite can handle,
00023 sends that over the connection where the target device loads and interprets it.
00024 The target device then packages any result, sends it to the host computer
00025 and the host computer prints the result.
00026 """
00027 
00028 ## @file
00029 #  @copybrief ipm
00030 
00031 ## @package ipm
00032 #  @brief Interactive PyMite
00033 #
00034 #
00035 #  An interactive command line that runs on a host computer that is connected
00036 #  to a target device that is running PyMite.  The host computer compiles the
00037 #  interactive statement and converts it to a form that PyMite can handle,
00038 #  sends that over the connection where the target device loads and interprets it.
00039 #  The target device then packages any result, sends it to the host computer
00040 #  and the host computer prints the result.
00041 
00042 import cmd, dis, getopt, os, subprocess, sys, time
00043 import pmImgCreator
00044 
00045 
00046 __usage__ = """USAGE:
00047     ipm.py -[d|s /dev/tty] --[desktop | serial=/dev/tty [baud=19200]]
00048 
00049     -h          Prints this usage message.
00050     --help
00051 
00052     -d          Specifies a desktop connection; uses pipes to send/receive bytes
00053     --desktop   to/from the target, which is the vm also running on the desktop.
00054                 ipm spawns the vm and runs ipm-desktop as a subprocess.
00055 
00056     -s <port> [<baud>] Specifies the port (device) for a serial connection.
00057                 <port> resembles `com5` on Win32 or `/dev/cu.usbmodem1912`.
00058                 Optional argument, <baud>, defaults to 19200.
00059 
00060     --serial=<port> Specifies the port (device) for a serial connection.
00061 
00062     --baud=<baud>   Specifies the baud rate for a serial connection.
00063 
00064 REQUIREMENTS:
00065 
00066     - pySerial package from http://pyserial.sourceforge.net/
00067     """
00068 
00069 NEED_PYSERIAL = "Install the pySerial module from http://pyserial.sourceforge.net/"
00070 if not sys.platform.lower().startswith("win"):
00071     PMVM_EXE = "../platform/desktop/main.out"
00072 else:
00073     PMVM_EXE = "../platform/windows/Debug/pymite.exe"
00074 IPM_PROMPT = "ipm> "
00075 COMPILE_FN = "<ipm>"
00076 COMPILE_MODE = "single"
00077 INIT_MESSAGE = """Python-on-a-Chip is Copyright 2003, 2006, 2007, 2009 Dean Hall and others.
00078 Python-on-a-Chip is licensed under the GNU LESSER GENERAL PUBLIC LICENSE V 2.1
00079 PyMite is Copyright 2003, 2006, 2007, 2009 Dean Hall.  
00080 PyMite is licensed under the GNU GENERAL PUBLIC LICENSE V 2.
00081 This software is offered with NO WARRANTY.  See LICENSE for details.
00082 """
00083 HELP_MESSAGE = """Type the Python code that you want to run on the target device.
00084 If you see no prompt, type two consecutive returns to exit multiline mode.
00085 Type Ctrl+C to interrupt and Ctrl+D to quit (or Ctrl+Z <enter> on Win32).
00086 """
00087 
00088 REPLY_TERMINATOR = '\x04'
00089 
00090 if sys.platform.lower().startswith("win"):
00091     EOF_KEY = 'Z'
00092 else:
00093     EOF_KEY = 'D'
00094 
00095 
00096 class Connection(object):
00097     def open(self,): raise NotImplementedError
00098     def read(self,): raise NotImplementedError
00099     def write(self, msg): raise NotImplementedError
00100     def close(self,): raise NotImplementedError
00101 
00102 
00103 class PipeConnection(Connection):
00104     """Provides ipm-host to target connection over stdio pipes on the desktop.
00105     This connection should work on any POSIX-compliant OS.
00106     The ipm-device must be spawned as a subprocess
00107     (the executable created when PyMite was built with PLATFORM=desktop).
00108     """
00109     def __init__(self, target=PMVM_EXE):
00110         self.open(target)
00111 
00112 
00113     def open(self, target):
00114         self.child = subprocess.Popen(target,
00115                                       bufsize=-1,
00116                                       stdin=subprocess.PIPE,
00117                                       stdout=subprocess.PIPE,
00118                                       stderr=subprocess.PIPE,
00119                                       )
00120 
00121 
00122     def read(self,):
00123         # If the child process is not alive, read in everything from the buffer.
00124         # It will usually be an exception message from the target
00125         # TODO
00126 
00127         # Collect all characters up to and including the ipm reply terminator
00128         chars = []
00129         c = ''
00130         while c != REPLY_TERMINATOR:
00131             c = self.child.stdout.read(1)
00132             if c == '':
00133                 # DEBUG: uncomment the next line to print the child's return val
00134                 #print "DEBUG: child returncode = %s\n" % hex(self.child.poll())
00135                 break
00136             chars.append(c)
00137         msg = "".join(chars)
00138         return msg
00139 
00140 
00141     def write(self, msg):
00142         self.child.stdin.write(msg)
00143         self.child.stdin.flush()
00144 
00145 
00146     def close(self,):
00147         self.write("\0")
00148 
00149 
00150 class SerialConnection(Connection):
00151     """Provides ipm-host to target connection over a serial device.
00152     This connection should work on any platform that PySerial supports.
00153     The ipm-device must be running at the same baud rate (19200 default).
00154     """
00155 
00156     def __init__(self, serdev="/dev/cu.SLAB_USBtoUART", baud=19200):
00157         try:
00158             import serial
00159         except Exception, e:
00160             print NEED_PYSERIAL
00161             raise e
00162 
00163         self.s = serial.Serial(serdev, baud)
00164         self.s.setDTR(False)
00165         self.s.setRTS(False)
00166         self.s.setTimeout(1)
00167         time.sleep(0.1)
00168 
00169     def read(self,):
00170         # Collect all characters up to and including the ipm reply terminator
00171         return self.s.readline() #eol=REPLY_TERMINATOR)
00172 
00173 
00174     def write(self, msg):
00175         self.s.write(msg)
00176         self.s.flush()
00177 
00178 
00179     def close(self,):
00180         self.s.close()
00181 
00182 
00183 class Interactive(cmd.Cmd):
00184     """The interactive command line parser accepts typed input line-by-line.
00185     If a statement requires multiple lines to complete,  the input
00186     is collected until two sequential end-of-line characters are received.
00187     """
00188     ipmcommands = ("?", "help", "load",)
00189 
00190 
00191     def __init__(self, conn):
00192         cmd.Cmd.__init__(self,)
00193         self.prompt = IPM_PROMPT
00194         self.conn = conn
00195         self.pic = pmImgCreator.PmImgCreator()
00196 
00197 
00198     def do_help(self, *args):
00199         """Prints the help message.
00200         """
00201         self.stdout.write(HELP_MESSAGE)
00202 
00203 
00204     def do_load(self, *args):
00205         """Loads a module from the host to the target device.
00206         """
00207 
00208         # Ensure the filename arg names a python source file
00209         fn = args[0]
00210         if not os.path.exists(fn):
00211             self.stdout.write('File "%s" does not exist in %s.\n' 
00212                               % (fn, os.getcwd()))
00213             return
00214         if not fn.endswith(".py"):
00215             self.stdout.write('Error using "load <module>": '
00216                               'module must be a ".py" source file.\n')
00217             return
00218 
00219         src = open(fn).read()
00220         code = compile(src, fn, "exec")
00221  
00222         img = self.pic.co_to_str(code)
00223  
00224         self.conn.write(img)
00225         while True:
00226             str = self.conn.read()
00227             self.stdout.write(str)
00228             if len(str) == 0:
00229                 break
00230 
00231 
00232     def onecmd(self, line):
00233         """Gathers one interactive line of input (gets more lines as needed).
00234         """
00235         # Ignore empty line, continue interactive prompt
00236         if not line:
00237             return
00238 
00239         # Handle ctrl+D (End Of File) input, stop interactive prompt
00240         if line == "EOF":
00241             self.conn.close()
00242 
00243             # Do this so OS prompt is on a new line
00244             self.stdout.write("\n")
00245 
00246             # Quit the run loop
00247             self.stop = True
00248             return True
00249 
00250         # Handle ipm-specific commands
00251         if line.split()[0] in Interactive.ipmcommands:
00252             cmd.Cmd.onecmd(self, line)
00253             return
00254 
00255         # Gather input from the interactive line
00256         codeobj = None
00257         while not codeobj:
00258 
00259             # Try to compile the given line
00260             try:
00261                 codeobj = compile(line, COMPILE_FN, COMPILE_MODE)
00262 
00263             # Get more input if syntax error reports unexpected end of file
00264             except SyntaxError, se:
00265 
00266                 # Print any other syntax error
00267                 if not se.msg.startswith("unexpected EOF while parsing"):
00268                     self.stdout.write("%s:%s\n" % (se.__class__.__name__, se))
00269                     return
00270 
00271                 # Restore the newline chopped by cmd.py:140
00272                 line += "\n"
00273 
00274                 # Get more input if needed
00275                 while not line.endswith("\n\n"):
00276                     line += self.stdin.readline()
00277 
00278             # Print any other exception
00279             except Exception, e:
00280                 self.stdout.write("%s:%s\n" % (e.__class__.__name__, e))
00281                 return
00282 
00283         # DEBUG: Uncomment the next line to print the statement's bytecodes
00284         #dis.disco(codeobj)
00285 
00286         # Convert to a code image
00287         try:
00288             codeimg = self.pic.co_to_str(codeobj)
00289 
00290         # Print any conversion errors
00291         except Exception, e:
00292             self.stdout.write("%s:%s\n" % (e.__class__.__name__, e))
00293 
00294         # Otherwise send the image and print the reply
00295         else:
00296 
00297             # DEBUG: Uncomment the next line to print the size of the code image
00298             # print "DEBUG: len(codeimg) = ", len(codeimg)
00299             # DEBUG: Uncomment the next line to print the code image
00300             # print "DEBUG: codeimg = ", repr(codeimg)
00301 
00302             try:
00303                 self.conn.write(codeimg)
00304             except Exception, e:
00305                 self.stdout.write(
00306                     "Connection write error, type Ctrl+%s to quit.\n" % EOF_KEY)
00307 
00308             rv = self.conn.read()
00309             if rv == '':
00310                 self.stdout.write(
00311                     "Connection read error, type Ctrl+%s to quit.\n" % EOF_KEY)
00312             else:
00313                 if rv.endswith(REPLY_TERMINATOR):
00314                     self.stdout.write(rv[:-1])
00315                 else:
00316                     self.stdout.write(rv)
00317 
00318 
00319     def run(self,):
00320         """Runs the command loop and handles keyboard interrupts (ctrl+C).
00321         The command loop is what calls self.onecmd().
00322         """
00323 
00324         print INIT_MESSAGE,
00325         print HELP_MESSAGE,
00326 
00327         self.stop = False
00328         while not self.stop:
00329             try:
00330                 self.cmdloop()
00331             except KeyboardInterrupt, ki:
00332                 print "\n", ki.__class__.__name__
00333                 # TODO: check connection?
00334 
00335 
00336 def parse_cmdline():
00337     """Parses the command line for options.
00338     """
00339     baud = 19200
00340     Conn = PipeConnection
00341     serdev = None
00342 
00343     try:
00344         opts, args = getopt.getopt(sys.argv[1:], "dhs",
00345             ["desktop", "help", "serial=", "baud="])
00346     except Exception, e:
00347         print __usage__
00348         sys.exit()
00349 
00350     if not opts:
00351         print __usage__
00352         sys.exit()
00353 
00354     for opt in opts:
00355         if opt[0] == "-d" or opt[0] == "--desktop":
00356             Conn = PipeConnection
00357         elif opt[0] == "-s":
00358             Conn = SerialConnection
00359             serdev = args[0]
00360             if len(args) > 1:
00361                 baud = int(args[1])
00362         elif opt[0] == "--serial":
00363             Conn = SerialConnection
00364             serdev = opt[1]
00365         elif opt[0] == "--baud":
00366             assert serdev, "--serial must be specified before --baud."
00367             baud = int(opt[1])
00368         else:
00369             print __usage__
00370             sys.exit(0)
00371 
00372     if Conn == SerialConnection:
00373         c = Conn(serdev, baud)
00374     else:
00375         c = Conn()
00376 
00377     return c
00378 
00379 
00380 def main():
00381     conn = parse_cmdline()
00382     i = Interactive(conn)
00383     while True:
00384         i.do_load("robot.py")
00385         # Prompt for enter
00386         sys.stdout.write("Press enter to re-run your program...")
00387         sys.stdin.readline()
00388         # Reset PIC
00389         conn.s.setDTR(True)
00390         conn.s.setRTS(True)
00391         time.sleep(0.1)
00392         conn.s.setDTR(False)
00393         conn.s.setRTS(False)
00394         time.sleep(0.1)
00395     #i.run()
00396 
00397 
00398 def ser_test():
00399     """Test ipm over serial connection directly.
00400     """
00401     try:
00402         import serial
00403     except Exception, e:
00404         print NEED_PYSERIAL
00405         raise e
00406 
00407     pic = pmImgCreator.PmImgCreator()
00408     serconn = serial.Serial("/dev/cu.SLAB_USBtoUART", 19200)
00409     serconn.setTimeout(2)
00410 
00411     testcode = (
00412         'print "Hello"\n',
00413         'import sys\n',
00414         'print sys.heap()\n',
00415         )
00416 
00417     for line in testcode:
00418         print "compiling ``%s``" % line
00419         codeobj = compile(line, COMPILE_FN, COMPILE_MODE)
00420         codeimg = pic.co_to_str(codeobj)
00421         print "codeimg is %d bytes" % len(codeimg)
00422         print "sending codeimg..."
00423         serconn.write(codeimg)
00424         reply = serconn.readline(eol=REPLY_TERMINATOR)
00425         print "reply is %d bytes" % len(reply)
00426         print "reply is:\n%s" % reply
00427 
00428 
00429 if __name__ == "__main__":
00430     main()

Generated on Mon Oct 18 07:40:46 2010 for Python-on-a-chip by  doxygen 1.5.9