ipm.py

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

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