00001
00002
00003
00004
00005
00006
00007
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"
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'
00033 FILECHARACTER = '\x02'
00034 ENDCHARACTER = '\x04'
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
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
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
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:
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
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":
00192 self.__convert_nl = self.CONVERT_CRLF
00193 elif convert_nl.upper() == "CR":
00194 self.__convert_nl = self.CONVERT_CR
00195 elif convert_nl.upper() == "LF":
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
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")
00230
00231 fnRemote = ''
00232 fnStart = False
00233 line = ''
00234 while not self.__quit:
00235 data = self.read()
00236 if data == self.STARTCHARACTER:
00237 f.close()
00238 f = open(fn, "w")
00239 elif data == self.ENDCHARACTER:
00240 break
00241 elif data == self.FILECHARACTER:
00242 fnStart = not fnStart
00243 elif data == '\n':
00244 if self.__convert_nl == self.CONVERT_CRLF:
00245 line += '\r\n'
00246 elif self.__convert_nl == self.CONVERT_CR:
00247 line += '\r'
00248 elif self.__convert_nl == self.CONVERT_LF:
00249 line += '\n'
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()
00264
00265
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
00273 os.rename(fn, filename)
00274
00275
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])
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
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
00354 if False:
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