gSerial.py

Go to the documentation of this file.
00001 #!/usr/local/bin/python
00002 #
00003 #                               Copyright 2005
00004 #                                     by
00005 #                        The Board of Trustees of the
00006 #                     Leland Stanford Junior University.
00007 #                            All rights reserved.
00008 #
00009 
00010 __facility__ = "Online"
00011 __abstract__ = "GLAST LAT serial port wrapper classes"
00012 __author__   = "R. Claus <Claus@SLAC.Stanford.edu> SLAC - GLAST LAT I&T/Online"
00013 __date__     = "12/27/2005" # Created
00014 __version__  = "$Revision: 1.2 $"
00015 __release__  = "$Name: HEAD $"
00016 __credits__  = "SLAC"
00017 
00018 import LICOS.copyright_SLAC
00019 
00020 
00021 import getopt
00022 import sys
00023 import os
00024 import time
00025 from   ConfigParser import ConfigParser
00026 from   serial       import Serial
00027 
00028 
00029 class SerialCommon(Serial):
00030   """!\brief A helper class to add some protocol to the serial byte stream.
00031   """
00032   STARTCHARACTER = '\x01'   #ctrl+A = SOH = Start of Heading
00033   FILECHARACTER  = '\x02'   #ctrl+B = STX = Start of Text
00034   ENDCHARACTER   = '\x04'   #ctrl+D = EOT = End of Transmission
00035 
00036   def __init__(self, config, echo = None, timeout = None):
00037     """!\brief  Initialize the class with defaults from a configuration file
00038     and/or the command line.
00039 
00040     \param config - A ConfigParser configuration object
00041     \param echo   - Option to override the configuration file's echo value
00042     """
00043     self.__config = config
00044 
00045     # Initialize the defaults
00046     self.__port       = 0
00047     self.__baudrate   = 9600
00048     self.__rtscts     = 0
00049     self.__xonxoff    = 0
00050     try:
00051       self._echo      = int(echo)
00052     except:
00053       self._echo      = echo
00054     self._debug       = 0
00055 
00056     self.__serialOptions(config)
00057 
00058     # Open the serial port
00059     try:
00060       Serial.__init__(self, self.__port, self.__baudrate, timeout=timeout,
00061                       rtscts=self.__rtscts, xonxoff=self.__xonxoff)
00062     except:
00063       sys.stderr.write("Could not open port\n")
00064       raise
00065 
00066 
00067   def __serialOptions(self, config):
00068     """
00069     Serial line options the [serial] block of the configuration file can be:
00070     port=PORT:             Port, a number, default = 0 or a device name
00071     baud=BAUD:             Baud rate, default 9600
00072     rtscts=0/1:            En/dis-able RTS/CTS flow control (default: disabled)
00073     xonxoff=0/1:           En/dis-able software flow control (default: disabled)
00074     echo=0/1:              En/dis-able local echo (default: disabled)
00075     debug=0/1:             En/dis-able
00076     convert_nl=CR/LF/CRLF: Convert newline character(s) to one of CR, LF, CRLF
00077                            (default: OS dependent)
00078     """
00079     cSect = config.sections()
00080     if "serial" not in cSect:  return   # Take the defaults
00081 
00082     opts = config.options("serial")
00083 
00084     if "port" in opts:
00085       port = config.get("serial", "port")
00086       try:
00087         self.__port = int(port)
00088       except ValueError:
00089         self.__port = port
00090 
00091     if "baudrate" in opts:
00092       baudrate = config.get("serial", "baudrate")
00093       try:
00094         self.__baudrate = int(baudrate)
00095       except ValueError:
00096         raise ValueError, "Baudrate must be a integer number, not %r" % baudrate
00097 
00098     if "rtscts" in opts:
00099       self.__rtscts  = config.get("serial", "rtscts") != "0"
00100 
00101     if "xonxoff" in opts:
00102       self.__xonxoff = config.get("serial", "xonxoff") != "0"
00103 
00104     if self._echo is None:     # Allow command line option to override
00105       if "echo" in opts:
00106         self._echo   = config.get("serial", "echo") != "0"
00107       else:
00108         self._echo   = False
00109 
00110     if "debug" in opts:
00111       self._debug    = config.get("serial", "debug") != "0"
00112 
00113   def getConfig(self):
00114     """!\brief Return the configuration ConfigParser object
00115     """
00116     return self.__config
00117 
00118 
00119 class SerialSender(SerialCommon):
00120   """!\brief A helper class to add some protocol to the serial byte stream.
00121   """
00122   def __init__(self, config, echo = False):
00123     """!\brief  Initialize the class with defaults from a configuration file
00124     and/or the command line.
00125 
00126     \param config - A ConfigParser configuration object
00127     \param echo   - Option to override the configuration file's echo value
00128     """
00129     SerialCommon.__init__(self, config, echo)
00130 
00131   def begin(self, filename = None):
00132     """!\brief Announce that a block of data is about to be sent.
00133     Optionally request that the other side tags it with a specific file name.
00134 
00135     \param filename Optional filename
00136     """
00137     self.write(self.STARTCHARACTER)
00138     if filename is not None:
00139       self.write(self.FILECHARACTER + fn + self.FILECHARACTER)
00140 
00141   def send(self, buf):
00142     """!\brief Send a buffer of data.
00143     Optionally print the buffer for debugging
00144 
00145     \param buf Buffer of data to send
00146     """
00147     if self._echo:
00148       if self._debug:  print "%r" % buf
00149       else:            print buf,
00150     self.write(buf)
00151 
00152   def end(self):
00153     """!\brief Announce the end of the block of data.
00154     """
00155     self.write(self.ENDCHARACTER)
00156 
00157 
00158 class SerialReceiver(SerialCommon):
00159   """!\brief A helper class to handle some protocol on the serial byte stream.
00160   """
00161   CONVERT_CRLF   = 2
00162   CONVERT_CR     = 1
00163   CONVERT_LF     = 0
00164 
00165   def __init__(self, config, echo = False):
00166     """!\brief  Initialize the class with defaults from a configuration file.
00167 
00168     \param config - A ConfigParser configuration object
00169     \param echo   - Option to override the configuration file's echo value
00170     """
00171     SerialCommon.__init__(self, config, echo, timeout=1)
00172 
00173     self.__additionalOptions(config)
00174 
00175     self.__quit   = False
00176 
00177   def __additionalOptions(self, config):
00178     """
00179     Serial line options the [serial] block of the configuration file can be:
00180     convert_nl=CR/LF/CRLF: Convert newline character(s) to one of CR, LF, CRLF
00181                            (default: OS dependent)
00182     """
00183     cSect = config.sections()
00184     if "serial" not in cSect:  return    # Take the defaults
00185 
00186     opts = config.options("serial")
00187 
00188     self.__convert_nl = None
00189     if "convert_nl" in opts:
00190       convert_nl     = config.get("serial", "convert_nl")
00191       if   convert_nl.upper() == "CRLF": # Windows mode
00192         self.__convert_nl = self.CONVERT_CRLF
00193       elif convert_nl.upper() == "CR":   # Mac mode
00194         self.__convert_nl = self.CONVERT_CR
00195       elif convert_nl.upper() == "LF":   # Unix mode
00196         self.__convert_nl = self.CONVERT_LF
00197 
00198   def quit(self):
00199     """!\brief Call this method from another thread to force the receive loop
00200     to exit.
00201     """
00202     self.__quit = True
00203 
00204   def receive(self, filename = None):
00205     """!\brief Receive a block of data from the serial port and write
00206     it to a file.
00207 
00208     Receive a block of data from the serial port and write it to a
00209     file.  In order of decreasing precedence, the filename is given by:
00210     - an optional argument to this method
00211     - a name embedded in the serial byte stream
00212     - the name of the program executing this method followed by an
00213       underscore and a timestamp with a '.txt' extension, e.g.,
00214       'serialReceiver_12345678.txt'.  The timestamp used is what is
00215       returned by Python's time() method, but with the decimal point
00216       separator removed.
00217 
00218     To avoid conflicts while the file is being written, the data is
00219     initially written to a temporary file.  When the temporary file is
00220     complete (as provided by a signal in the serial byte stream), it
00221     is renamed with the filename as described above.
00222 
00223     \param filename - Optional filename to contain the data
00224     """
00225     # Use a temporary file to write the byte stream to
00226     s = '/'
00227     if os.name is "nt":  s = '\\'
00228     fn = sys.argv[0].split('.')[-2].split(s)[-1] + ".txt"
00229     f = open(fn, "w")                   # In case STARTCHARACTER is never seen
00230 
00231     fnRemote    = ''
00232     fnStart     = False
00233     line        = ''
00234     while not self.__quit:
00235       data = self.read()              # Get one character
00236       if   data == self.STARTCHARACTER:
00237         f.close()
00238         f = open(fn, "w")
00239       elif data == self.ENDCHARACTER:
00240         break                           # Exit if exit character seen
00241       elif data == self.FILECHARACTER:
00242         fnStart = not fnStart
00243       elif data == '\n':                # Convert newlines
00244         if   self.__convert_nl == self.CONVERT_CRLF:
00245           line += '\r\n'         #make it a CR+LF
00246         elif self.__convert_nl == self.CONVERT_CR:
00247           line += '\r'           #make it a CR
00248         elif self.__convert_nl == self.CONVERT_LF:
00249           line += '\n'           #make it a LF
00250         else:
00251           line += data
00252         if self._echo:
00253           if self._debug:  print "%r" % line
00254           else:            print line,
00255         f.write(line)
00256         line = ''
00257       else:
00258         if fnStart:
00259           fnRemote += data
00260         else:
00261           line += data
00262 
00263     f.close()                           # In case Exit
00264 
00265     # Indicate we're done with the file by renaming it to the target name
00266     if filename is None:
00267       filename = fnRemote
00268     if len(filename) == 0:
00269       fnTuple  = fn.split('.')
00270       tijd     = str(time.time()).split('.')
00271       filename = fnTuple[0] + "_" + tijd[0] + tijd[1] + "." + fnTuple[1]
00272     # Todo: Deal path issues here
00273     os.rename(fn, filename)
00274 
00275     # ToDo: Probably should delete zero length files here
00276 
00277     return self.__quit
00278 
00279 
00280 if __name__ == "__main__":
00281   def senderUsage():
00282     """!\brief Return sender program usage information
00283     """
00284     s = '/'
00285     if os.name is 'nt':  s = '\\'
00286     name = sys.argv[0].split('.')[-2].split(s)[-1]
00287     return "\n\n%s is used to copy a file over a serial port connection.\n" % \
00288            (name)
00289 
00290   def receiverUsage():
00291     """!\brief Return receiver program usage information
00292     """
00293     s = '/'
00294     if os.name is 'nt':  s = '\\'
00295     name = sys.argv[0].split('.')[-2].split(s)[-1]
00296     return """\n
00297     %s is used to receive data from a serial port
00298     connection and store it in a file.  The file's name will be:
00299 
00300     - command line value (always take precedence)
00301     - a filename transmitted by the other side
00302     - %s_<timestamp>.txt (default if neither side specifies a name)
00303 
00304     """ % (name, name)
00305 
00306   def senderTest(cfgFile, filename):
00307     """!\brief Test code that copies a file over a serial port connection.
00308 
00309     \param cfgFile  - The filename of a .cfg file
00310     \param filename - The name of the file to be copied
00311     """
00312     config = ConfigParser()
00313     config.read(cfgFile)
00314 
00315     sender = SerialSender(config)
00316     sender.begin(filename.split('/')[-1])   # Just the filename, not the path
00317 
00318     f = open(filename, "r")
00319     for line in f.xreadlines():
00320       sender.send(line)
00321 
00322     sender.end()
00323     f.close()
00324 
00325   def receiverTest(cfgFile, filename):
00326     config = ConfigParser()
00327     config.read(cfgFile)
00328 
00329     receiver = SerialReceiver(config)
00330 
00331     # Keep reading files until somebody aborts us
00332     while True:
00333       receiver.receive(filename)
00334 
00335     receiver.close()
00336 
00337   def handleOptions(mandatory, optional=[], switches=[], usage=None):
00338     from LICOS.util.gOptions import Options
00339 
00340     if usage is None:
00341       def usage():  pass
00342 
00343     options = Options(mandatory, optional, switches)
00344     try:
00345       options.parse()
00346     except Exception, msg:
00347       options.usage(usage())
00348       raise Exception, msg
00349 
00350     return options
00351 
00352   #--
00353   # Options, first list mandatory, second list optional
00354   if False: #True:
00355     options = handleOptions(["config", "filename"], usage=senderUsage)
00356 
00357     senderTest(options.config, options.filename)
00358   else:
00359     options = handleOptions(["config"], ["filename"], usage=receiverUsage)
00360 
00361     receiverTest(options.config, options.filename)
00362 

Generated on Thu Apr 27 20:52:41 2006 for LICOS L02-01-00 by doxygen 1.4.6-NO